Skip to content

Commit

Permalink
Add tree filter by icons menu
Browse files Browse the repository at this point in the history
  • Loading branch information
swiftyfinch committed May 5, 2024
1 parent 0d68494 commit 50dbc16
Show file tree
Hide file tree
Showing 11 changed files with 168 additions and 26 deletions.
8 changes: 4 additions & 4 deletions Sources/XTreeKit/Managers/TreeManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import Fish
// MARK: - Interface

public struct TreeFilterOptions {
var roots: [String]
var contains: [String]
var except: [String]
var maxHeight: Int?
public var roots: [String]
public var contains: [String]
public var except: [String]
public var maxHeight: Int?

public init(
roots: [String],
Expand Down
20 changes: 18 additions & 2 deletions XTree/XTree.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
238BE9CB2BCEE76F00DCFBEA /* NSColor+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238BE9CA2BCEE76F00DCFBEA /* NSColor+Hex.swift */; };
238BE9CF2BCEE7CE00DCFBEA /* TreeRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238BE9CE2BCEE7CE00DCFBEA /* TreeRowView.swift */; };
238BE9D12BCEE84200DCFBEA /* TreeRowTitleFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238BE9D02BCEE84200DCFBEA /* TreeRowTitleFormatter.swift */; };
238C22B42BE675BB004CE2E2 /* IconsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238C22B32BE675BB004CE2E2 /* IconsMenu.swift */; };
238C22B62BE6B998004CE2E2 /* Color+Hex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238C22B52BE6B998004CE2E2 /* Color+Hex.swift */; };
238C80672BC9A7C600E515A8 /* XTreeApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238C80662BC9A7C600E515A8 /* XTreeApp.swift */; };
238C806B2BC9A7C700E515A8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 238C806A2BC9A7C700E515A8 /* Assets.xcassets */; };
238C806E2BC9A7C700E515A8 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 238C806D2BC9A7C700E515A8 /* Preview Assets.xcassets */; };
Expand All @@ -43,6 +45,8 @@
238BE9CA2BCEE76F00DCFBEA /* NSColor+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSColor+Hex.swift"; sourceTree = "<group>"; };
238BE9CE2BCEE7CE00DCFBEA /* TreeRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TreeRowView.swift; sourceTree = "<group>"; };
238BE9D02BCEE84200DCFBEA /* TreeRowTitleFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TreeRowTitleFormatter.swift; sourceTree = "<group>"; };
238C22B32BE675BB004CE2E2 /* IconsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconsMenu.swift; sourceTree = "<group>"; };
238C22B52BE6B998004CE2E2 /* Color+Hex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Hex.swift"; sourceTree = "<group>"; };
238C80632BC9A7C600E515A8 /* XTree.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = XTree.app; sourceTree = BUILT_PRODUCTS_DIR; };
238C80662BC9A7C600E515A8 /* XTreeApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XTreeApp.swift; sourceTree = "<group>"; };
238C806A2BC9A7C700E515A8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
Expand Down Expand Up @@ -73,8 +77,6 @@
children = (
2344E6F82BD4FEFB00E36A06 /* MainView.swift */,
238BE9BB2BCEE0D900DCFBEA /* DragAndDropView.swift */,
23F1D2FA2BD972E300C03134 /* FiltersView.swift */,
23F1D3042BD9840000C03134 /* ToolBarView.swift */,
);
path = Main;
sourceTree = "<group>";
Expand Down Expand Up @@ -126,19 +128,31 @@
isa = PBXGroup;
children = (
238BE9CA2BCEE76F00DCFBEA /* NSColor+Hex.swift */,
238C22B52BE6B998004CE2E2 /* Color+Hex.swift */,
);
path = Extensions;
sourceTree = "<group>";
};
238BE9D22BCEE86E00DCFBEA /* Modules */ = {
isa = PBXGroup;
children = (
238C22B22BE675A8004CE2E2 /* ToolBar */,
2344E6F72BD4FEEE00E36A06 /* Main */,
238BE9C02BCEE1A400DCFBEA /* TreeView */,
);
path = Modules;
sourceTree = "<group>";
};
238C22B22BE675A8004CE2E2 /* ToolBar */ = {
isa = PBXGroup;
children = (
23F1D3042BD9840000C03134 /* ToolBarView.swift */,
238C22B32BE675BB004CE2E2 /* IconsMenu.swift */,
23F1D2FA2BD972E300C03134 /* FiltersView.swift */,
);
path = ToolBar;
sourceTree = "<group>";
};
238C805A2BC9A7C600E515A8 = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -273,6 +287,7 @@
23F1D2FF2BD97CB700C03134 /* TreeBuilder.swift in Sources */,
238BE9D12BCEE84200DCFBEA /* TreeRowTitleFormatter.swift in Sources */,
2344E6F92BD4FEFB00E36A06 /* MainView.swift in Sources */,
238C22B62BE6B998004CE2E2 /* Color+Hex.swift in Sources */,
238BE9A32BCC4C5D00DCFBEA /* DesignSystem.swift in Sources */,
238BE9BF2BCEE10000DCFBEA /* TreeViewModelBuilder.swift in Sources */,
238BE9C62BCEE27900DCFBEA /* TreeViewController.swift in Sources */,
Expand All @@ -284,6 +299,7 @@
23F1D3032BD980B000C03134 /* Vault.swift in Sources */,
238BE9A72BCC4F5700DCFBEA /* AppDelegate.swift in Sources */,
238C80672BC9A7C600E515A8 /* XTreeApp.swift in Sources */,
238C22B42BE675BB004CE2E2 /* IconsMenu.swift in Sources */,
23F1D2F42BD964D000C03134 /* SelectionTreeRowView.swift in Sources */,
238BE9CF2BCEE7CE00DCFBEA /* TreeRowView.swift in Sources */,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1530"
version = "1.7">
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
Expand Down
7 changes: 7 additions & 0 deletions XTree/XTree/Common/Extensions/Color+Hex.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import SwiftUI

extension Color {
init(hex: UInt) {
self.init(nsColor: NSColor(hex: hex))
}
}
4 changes: 4 additions & 0 deletions XTree/XTree/Common/Extensions/NSColor+Hex.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import AppKit

extension NSColor {
convenience init(hex: UInt) {
self.init(hex: hex, alpha: 1)
}

convenience init(hex: UInt, alpha: Double = 1) {
self.init(
srgbRed: Double((hex >> 16) & 0xFF) / 255,
Expand Down
15 changes: 14 additions & 1 deletion XTree/XTree/Modules/Main/MainView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct MainView: View {
}
.onChange(of: state.toolbar.sorting, update)
.onChange(of: state.toolbar.isCompressed, update)
.onChange(of: state.toolbar.icons, update)
}

private func update() {
Expand All @@ -59,14 +60,26 @@ struct MainView: View {
except: formatFilters(state.filters.except),
sorting: state.toolbar.sorting,
isCompressed: state.toolbar.isCompressed,
filterText: state.filters.filter
filterText: state.filters.filter,
hiddenIcons: state.toolbar.icons.hidden
) {
state.tree = $0
state.toolbar.icons = convertIcons($0?.icons)
state.toolbar.isProcessing = false
}
}

private func formatFilters(_ filters: String) -> [String] {
filters.components(separatedBy: ",").filter { !$0.isEmpty }
}

private func convertIcons(_ icons: [TreeNodeContent.Icon]?) -> [IconState] {
let hiddenIcons = state.toolbar.icons.hidden
return icons?.map { icon in
IconState(
icon: icon,
isHidden: hiddenIcons.contains(icon.sfSymbol)
)
} ?? []
}
}
File renamed without changes.
46 changes: 46 additions & 0 deletions XTree/XTree/Modules/ToolBar/IconsMenu.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import SwiftUI

struct IconState: Equatable {
let icon: TreeNodeContent.Icon
var isHidden: Bool
}

struct IconsMenu: View {
@Binding var state: [IconState]

var body: some View {
Menu {
ForEach(Array(state.enumerated()), id: \.offset) { index, icon in
Button(action: {
state[index].isHidden.toggle()
}, label: {
buildButtonLabel(icon)
})
}
} label: {
Image(systemName: "square.filled.on.square")
}
}

private func buildButtonLabel(_ state: IconState) -> some View {
HStack {
if state.isHidden {
Text("Show").foregroundStyle(.hidden)
Image(systemName: state.icon.sfSymbol).foregroundStyle(.hidden, .hidden)
} else {
Text("Hide")
Image(systemName: state.icon.sfSymbol)
.foregroundStyle(
Color(hex: state.icon.primaryColor),
state.icon.secondaryColor.map(Color.init(hex:)) ?? Color(hex: state.icon.primaryColor)
)
}
}
}
}

private extension ShapeStyle where Self == Color {
static var hidden: Color {
Color(hex: 0x9da1a7).opacity(0.5)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct ToolBarState {
var sorting = "Name"
var isCompressed = true
var isProcessing = false
var icons: [IconState] = []
}

struct ToolBarView: View {
Expand All @@ -31,6 +32,8 @@ struct ToolBarView: View {
.keyboardShortcut(.init(.init("F"), modifiers: .command))
.help(state.isFiltersBlockShown ? "Hide filters" : "Show filters")

IconsMenu(state: $state.icons)

Picker("", selection: $state.sorting) {
ForEach(state.sortingValues, id: \.self) { value in
HStack {
Expand Down Expand Up @@ -72,3 +75,9 @@ struct ToolBarView: View {
}
}
}

extension [IconState] {
var hidden: Set<String> {
Set(filter(\.isHidden).map(\.icon.sfSymbol))
}
}
80 changes: 63 additions & 17 deletions XTree/XTree/Modules/TreeView/TreeBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,50 @@ final class TreeBuilder: ObservableObject {
sorting: String,
isCompressed: Bool,
filterText: String,
hiddenIcons: Set<String>,
completion: @escaping (TreeViewState?) -> Void
) {
guard let fileURL else { return completion(nil) }
Task.detached {
do {
let tree = try await self.treeManager.print(
inputPath: fileURL.path,
filter: .init(
roots: roots,
contains: contains,
except: except,
maxHeight: nil
),
sort: Sort(rawValue: sorting.lowercased()) ?? .name,
needsCompress: isCompressed
)
self.userDefaultsStorage.fileURL = fileURL
let adjacentList = self.treeViewModelBuilder.buildAdjacentList(
root: tree,
var filter = TreeFilterOptions(roots: roots, contains: contains, except: except)
let sort = Sort(rawValue: sorting.lowercased()) ?? .name
let adjacentList = try await self.buildAdjacentList(
path: fileURL.path,
filter: filter,
sort: sort,
needsCompress: isCompressed,
filterText: filterText
)
self.userDefaultsStorage.fileURL = fileURL

let icons = self.icons(adjacentList: adjacentList)
let hiddenByIconTitles = adjacentList.filter {
guard let icon = $0.value.icon else { return false }
return hiddenIcons.contains(icon.sfSymbol)
}.map(\.value.title)

let filteredAdjacentList: [String: TreeNodeContent]
if hiddenByIconTitles.isEmpty {
filteredAdjacentList = adjacentList
} else {
filter.except += hiddenByIconTitles
filteredAdjacentList = try await self.buildAdjacentList(
path: fileURL.path,
filter: filter,
sort: sort,
needsCompress: isCompressed,
filterText: filterText
)
}

Task { @MainActor in
completion(TreeViewState(
id: fileURL.path,
root: tree.name,
adjacentList: adjacentList,
highlightText: filterText
root: ".",
adjacentList: filteredAdjacentList,
highlightText: filterText,
icons: icons
))
}
} catch {
Expand All @@ -64,4 +81,33 @@ final class TreeBuilder: ObservableObject {
}
}
}

private func buildAdjacentList(
path: String,
filter: TreeFilterOptions,
sort: Sort,
needsCompress: Bool,
filterText: String
) async throws -> [String: TreeNodeContent] {
let tree = try await self.treeManager.print(
inputPath: path,
filter: filter,
sort: sort,
needsCompress: needsCompress
)
return self.treeViewModelBuilder.buildAdjacentList(
root: tree,
filterText: filterText
)
}

private func icons(adjacentList: [String: TreeNodeContent]) -> [TreeNodeContent.Icon] {
adjacentList.values
.reduce(into: [:]) { map, content in
guard let icon = content.icon else { return }
map[icon.sfSymbol] = icon
}
.values
.sorted { $0.sfSymbol < $1.sfSymbol }
}
}
3 changes: 2 additions & 1 deletion XTree/XTree/Modules/TreeView/TreeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ struct TreeViewState {
let root: String
let adjacentList: [String: TreeNodeContent]
let highlightText: String?
let icons: [TreeNodeContent.Icon]
}

struct TreeNodeContent {
struct Icon {
struct Icon: Equatable {
let sfSymbol: String
let primaryColor: UInt
let secondaryColor: UInt?
Expand Down

0 comments on commit 50dbc16

Please sign in to comment.