From 01ef4ab3d78238462ca496f5b4fb933d99135d0b Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 00:27:47 +0800 Subject: [PATCH 01/11] Add breadcrumb dropdown menu --- CodeEdit.xcodeproj/project.pbxproj | 4 ++ CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 69 +++++++++++++++++++ CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 34 +++++---- .../Modules/WorkspaceClient/src/Live.swift | 8 +++ .../WorkspaceClient/src/Model/FileItem.swift | 9 ++- 5 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 1d8e4c58b..cea50cbae 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; + D7ACE43C27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */; }; D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */; }; D7E201B227E8D50000CB86D0 /* FindNavigatorModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */; }; @@ -122,6 +123,7 @@ D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; tabWidth = 4; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; + D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenu.swift; sourceTree = ""; }; D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorSearchBar.swift; sourceTree = ""; }; D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorModeSelector.swift; sourceTree = ""; }; @@ -221,6 +223,7 @@ children = ( 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */, 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */, + D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */, ); path = Breadcrumbs; sourceTree = ""; @@ -575,6 +578,7 @@ 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */, 289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */, + D7ACE43C27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */, 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */, diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift new file mode 100644 index 000000000..fbbf13303 --- /dev/null +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -0,0 +1,69 @@ +// +// BreadcrumbsMenu.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/24. +// + +import SwiftUI +import WorkspaceClient + +struct BreadcrumbsMenu: View { + @ObservedObject var workspace: WorkspaceDocument + private var parentFileItem: WorkspaceClient.FileItem? + private let title: String + private let image: String + private let color: Color + + init( + _ workspace: WorkspaceDocument, + title: String, + systemImage image: String, + color: Color = .secondary, + parentFileItem: WorkspaceClient.FileItem? = nil + ) { + self.workspace = workspace + self.title = title + self.image = image + self.color = color + self.parentFileItem = parentFileItem + } + + private func menuItem(_ item: WorkspaceClient.FileItem) -> some View { + if let children = item.children, !children.isEmpty { + // Folder + return AnyView( + Menu { + ForEach(children) { item in + menuItem(item) + } + } label: { + BreadcrumbsComponent(item.fileName, systemImage: "folder.fill") + } + .menuIndicator(.hidden) + .menuStyle(.borderlessButton) + ) + } else { + // File + return AnyView(Button { + workspace.openFile(item: item) + } label: { + BreadcrumbsComponent(item.fileName, systemImage: item.fileIcon, color: item.iconColor) + }) + } + } + + var body: some View { + Menu { + if let siblings = parentFileItem?.children { + ForEach(siblings, id: \.self) { item in + menuItem(item) + } + } + } label: { + BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) + } + .menuIndicator(.hidden) + .menuStyle(.borderlessButton) + } +} diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index 74eb1c302..7133fed45 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -17,6 +17,9 @@ struct BreadcrumbsView: View { @State private var projectName: String = "" + @State + private var fileItems: [WorkspaceClient.FileItem] = [] + @State private var folders: [String] = [] @@ -37,19 +40,16 @@ struct BreadcrumbsView: View { .foregroundStyle(Color(nsColor: .controlBackgroundColor)) ScrollView(.horizontal, showsIndicators: false) { HStack { - BreadcrumbsComponent( - projectName, - systemImage: "square.dashed.inset.filled", - color: .accentColor - ) - - chevron - - ForEach(folders, id: \.self) { folder in - BreadcrumbsComponent(folder, systemImage: "folder.fill") - chevron - } - BreadcrumbsComponent(fileName, systemImage: fileImage, color: file.iconColor) + ForEach(fileItems, id: \.self) { fileItem in + if fileItem.parent != nil { + chevron + } + BreadcrumbsMenu(workspace, + title: fileItem.fileName, + systemImage: fileItem.parent == nil ? "square.dashed.inset.filled" : fileItem.systemImage, + color: fileItem.parent == nil ? .accentColor : file.iconColor, + parentFileItem: fileItem.parent) + } } .padding(.horizontal, 12) } @@ -73,7 +73,13 @@ struct BreadcrumbsView: View { } private func fileInfo(_ file: WorkspaceClient.FileItem) { - guard let projURL = workspace.fileURL else { return } + self.fileItems = [] + var currentFile: WorkspaceClient.FileItem? = file + while currentFile != nil { + self.fileItems.insert(currentFile!, at: 0) + currentFile = currentFile!.parent + } + guard let projURL = workspace.fileURL else { return } let components = file.url.path .replacingOccurrences(of: projURL.path, with: "") .split(separator: "/") diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Live.swift b/CodeEditModules/Modules/WorkspaceClient/src/Live.swift index e637da138..1119634b4 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Live.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Live.swift @@ -37,6 +37,9 @@ public extension WorkspaceClient { } let newFileItem = FileItem(url: itemURL, children: subItems) + subItems?.forEach { + $0.parent = newFileItem + } items.append(newFileItem) flattenedFileItems[newFileItem.id] = newFileItem } @@ -45,6 +48,11 @@ public extension WorkspaceClient { } // initial load let fileItems = try loadFiles(fromURL: folderURL) + // workspace fileItem + let workspaceItem = FileItem(url: folderURL, children: fileItems) + fileItems.forEach { item in + item.parent = workspaceItem + } // By using `CurrentValueSubject` we can define a starting value. // The value passed during init it's going to be send as soon as the // consumer subscribes to the publisher. diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift index 3c2147e7b..e05df6a15 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift @@ -9,11 +9,12 @@ import Foundation import SwiftUI public extension WorkspaceClient { - struct FileItem: Hashable, Identifiable, Comparable, Codable { + class FileItem: Hashable, Identifiable, Comparable, Codable { // TODO: use a phantom type instead of a String public var id: String public var url: URL - public var children: [FileItem]? + public var children: [FileItem]? + public var parent: FileItem? public static let fileManger = FileManager.default public var systemImage: String { switch children { @@ -149,6 +150,10 @@ public extension WorkspaceClient { } } } + + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } } } From 870795c872e81061c32cc67577f860c81272719f Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 02:32:40 +0800 Subject: [PATCH 02/11] Sort dropdown menus --- CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index fbbf13303..de38211b0 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -30,7 +30,7 @@ struct BreadcrumbsMenu: View { } private func menuItem(_ item: WorkspaceClient.FileItem) -> some View { - if let children = item.children, !children.isEmpty { + if let children = item.children?.sortItems(foldersOnTop: true), !children.isEmpty { // Folder return AnyView( Menu { @@ -55,7 +55,7 @@ struct BreadcrumbsMenu: View { var body: some View { Menu { - if let siblings = parentFileItem?.children { + if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { ForEach(siblings, id: \.self) { item in menuItem(item) } From 17799eba1415f10e1c51007aaca4184e2caab43e Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 16:33:14 +0800 Subject: [PATCH 03/11] Fix breadcrumbs icon color --- CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index de38211b0..120e4e625 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -54,16 +54,20 @@ struct BreadcrumbsMenu: View { } var body: some View { - Menu { - if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { - ForEach(siblings, id: \.self) { item in - menuItem(item) + // Unable to set image's color in Menu, so using this tricky way. + ZStack { + BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) + Menu { + if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { + ForEach(siblings, id: \.self) { item in + menuItem(item) + } } + } label: { + EmptyView() } - } label: { - BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) + .menuIndicator(.hidden) + .menuStyle(.borderlessButton) } - .menuIndicator(.hidden) - .menuStyle(.borderlessButton) } } From d565052e03709b61c4f522b640d1bc0c8d0873fe Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 18:01:16 +0800 Subject: [PATCH 04/11] Remove Anyview and extract menuItem to a component --- CodeEdit.xcodeproj/project.pbxproj | 4 +++ CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 26 +------------- .../Breadcrumbs/BreadcrumbsMenuItem.swift | 34 +++++++++++++++++++ 3 files changed, 39 insertions(+), 25 deletions(-) create mode 100644 CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index cea50cbae..fb877327e 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; + D76D11CA27EF191C009FE61F /* BreadcrumbsMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */; }; D7ACE43C27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */; }; D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */; }; @@ -123,6 +124,7 @@ D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; tabWidth = 4; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; + D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenuItem.swift; sourceTree = ""; }; D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenu.swift; sourceTree = ""; }; D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorSearchBar.swift; sourceTree = ""; }; @@ -224,6 +226,7 @@ 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */, 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */, D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */, + D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */, ); path = Breadcrumbs; sourceTree = ""; @@ -561,6 +564,7 @@ B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */, B673FDAD27E8296A00795864 /* PressActionsModifier.swift in Sources */, 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */, + D76D11CA27EF191C009FE61F /* BreadcrumbsMenuItem.swift in Sources */, 04660F6427E3ACAF00477777 /* Appearances.swift in Sources */, 2859B94127EB5BC00069BE88 /* TerminalSettingsView.swift in Sources */, 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */, diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index 120e4e625..108836878 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -29,30 +29,6 @@ struct BreadcrumbsMenu: View { self.parentFileItem = parentFileItem } - private func menuItem(_ item: WorkspaceClient.FileItem) -> some View { - if let children = item.children?.sortItems(foldersOnTop: true), !children.isEmpty { - // Folder - return AnyView( - Menu { - ForEach(children) { item in - menuItem(item) - } - } label: { - BreadcrumbsComponent(item.fileName, systemImage: "folder.fill") - } - .menuIndicator(.hidden) - .menuStyle(.borderlessButton) - ) - } else { - // File - return AnyView(Button { - workspace.openFile(item: item) - } label: { - BreadcrumbsComponent(item.fileName, systemImage: item.fileIcon, color: item.iconColor) - }) - } - } - var body: some View { // Unable to set image's color in Menu, so using this tricky way. ZStack { @@ -60,7 +36,7 @@ struct BreadcrumbsMenu: View { Menu { if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { ForEach(siblings, id: \.self) { item in - menuItem(item) + BreadcrumbsMenuItem(workspace: workspace, fileItem: item) } } } label: { diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift new file mode 100644 index 000000000..b5228c7a4 --- /dev/null +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift @@ -0,0 +1,34 @@ +// +// BreadcrumbsMenuItem.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/26. +// + +import SwiftUI +import WorkspaceClient + +struct BreadcrumbsMenuItem: View { + @ObservedObject var workspace: WorkspaceDocument + var fileItem: WorkspaceClient.FileItem + + var body: some View { + if let children = fileItem.children?.sortItems(foldersOnTop: true), !children.isEmpty { + // Folder + Menu { + ForEach(children, id: \.self) { child in + BreadcrumbsMenuItem(workspace: workspace, fileItem: child) + } + } label: { + BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) + } + } else { + // File + Button { + workspace.openFile(item: fileItem) + } label: { + BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) + } + } + } +} From 4b270b3564f6869ae2f92da2380b966b94b85033 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 20:28:34 +0800 Subject: [PATCH 05/11] Adjust indent --- CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 70 +++++++++---------- .../Breadcrumbs/BreadcrumbsMenuItem.swift | 39 ++++++----- CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 44 +++++++++--- 3 files changed, 89 insertions(+), 64 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index 108836878..88c2d81c7 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -9,41 +9,41 @@ import SwiftUI import WorkspaceClient struct BreadcrumbsMenu: View { - @ObservedObject var workspace: WorkspaceDocument - private var parentFileItem: WorkspaceClient.FileItem? - private let title: String - private let image: String - private let color: Color + @ObservedObject var workspace: WorkspaceDocument + private var parentFileItem: WorkspaceClient.FileItem? + private let title: String + private let image: String + private let color: Color - init( - _ workspace: WorkspaceDocument, - title: String, - systemImage image: String, - color: Color = .secondary, - parentFileItem: WorkspaceClient.FileItem? = nil - ) { - self.workspace = workspace - self.title = title - self.image = image - self.color = color - self.parentFileItem = parentFileItem - } + init( + _ workspace: WorkspaceDocument, + title: String, + systemImage image: String, + color: Color = .secondary, + parentFileItem: WorkspaceClient.FileItem? = nil + ) { + self.workspace = workspace + self.title = title + self.image = image + self.color = color + self.parentFileItem = parentFileItem + } - var body: some View { - // Unable to set image's color in Menu, so using this tricky way. - ZStack { - BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) - Menu { - if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { - ForEach(siblings, id: \.self) { item in - BreadcrumbsMenuItem(workspace: workspace, fileItem: item) - } - } - } label: { - EmptyView() - } - .menuIndicator(.hidden) - .menuStyle(.borderlessButton) - } - } + var body: some View { + // Unable to set image's color in Menu, so using this tricky way. + ZStack { + BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) + Menu { + if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { + ForEach(siblings, id: \.self) { item in + BreadcrumbsMenuItem(workspace: workspace, fileItem: item) + } + } + } label: { + EmptyView() + } + .menuIndicator(.hidden) + .menuStyle(.borderlessButton) + } + } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift index b5228c7a4..ab4ae7fde 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift @@ -9,26 +9,27 @@ import SwiftUI import WorkspaceClient struct BreadcrumbsMenuItem: View { - @ObservedObject var workspace: WorkspaceDocument - var fileItem: WorkspaceClient.FileItem + @ObservedObject var workspace: WorkspaceDocument + var fileItem: WorkspaceClient.FileItem + @State var shouldLoadChildren: Bool = false var body: some View { - if let children = fileItem.children?.sortItems(foldersOnTop: true), !children.isEmpty { - // Folder - Menu { - ForEach(children, id: \.self) { child in - BreadcrumbsMenuItem(workspace: workspace, fileItem: child) - } - } label: { - BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) - } - } else { - // File - Button { - workspace.openFile(item: fileItem) - } label: { - BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) - } - } + if let children = fileItem.children?.sortItems(foldersOnTop: true), !children.isEmpty { + // Folder + Menu { + ForEach(children, id: \.self) { child in + BreadcrumbsMenuItem(workspace: workspace, fileItem: child) + } + } label: { + BreadcrumbsComponent(fileItem.fileName, systemImage: "folder.fill", color: .secondary) + } + } else { + // File + Button { + workspace.openFile(item: fileItem) + } label: { + BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) + } + } } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index 7133fed45..b13568c23 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -12,6 +12,7 @@ struct BreadcrumbsView: View { @ObservedObject var workspace: WorkspaceDocument +<<<<<<< HEAD let file: WorkspaceClient.FileItem @State @@ -34,22 +35,45 @@ struct BreadcrumbsView: View { self.workspace = workspace } +======= + @ObservedObject var workspace: WorkspaceDocument + let file: WorkspaceClient.FileItem + + @State private var projectName: String = "" + @State private var fileItems: [WorkspaceClient.FileItem] = [] + @State private var folders: [String] = [] + @State private var fileName: String = "" + @State private var fileImage: String = "doc" + + init(_ file: WorkspaceClient.FileItem, workspace: WorkspaceDocument) { + self.file = file + self.workspace = workspace + } + +>>>>>>> 5ed6842 (Adjust indent) var body: some View { ZStack(alignment: .leading) { Rectangle() .foregroundStyle(Color(nsColor: .controlBackgroundColor)) ScrollView(.horizontal, showsIndicators: false) { HStack { - ForEach(fileItems, id: \.self) { fileItem in - if fileItem.parent != nil { - chevron - } - BreadcrumbsMenu(workspace, - title: fileItem.fileName, - systemImage: fileItem.parent == nil ? "square.dashed.inset.filled" : fileItem.systemImage, - color: fileItem.parent == nil ? .accentColor : file.iconColor, - parentFileItem: fileItem.parent) - } + ForEach(fileItems, id: \.self) { fileItem in + if fileItem.parent != nil { + chevron + } + let color = fileItem.parent == nil + ? .accentColor + : fileItem.children?.isEmpty ?? true + ? fileItem.iconColor + : .secondary + BreadcrumbsMenu(workspace, + title: fileItem.fileName, + systemImage: fileItem.parent == nil + ? "square.dashed.inset.filled" + : fileItem.systemImage, + color: color, + parentFileItem: fileItem.parent) + } } .padding(.horizontal, 12) } From ce307a2153ebf6a825685eef7a8f64b8acf82497 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Sat, 26 Mar 2022 20:47:01 +0800 Subject: [PATCH 06/11] Clean up useless code and add comments --- CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 9 +++ .../Breadcrumbs/BreadcrumbsMenuItem.swift | 4 +- CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 70 +++++-------------- .../Modules/WorkspaceClient/src/Live.swift | 16 ++--- .../WorkspaceClient/src/Model/FileItem.swift | 10 +-- 5 files changed, 42 insertions(+), 67 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index 88c2d81c7..79b4a3d7c 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -9,10 +9,19 @@ import SwiftUI import WorkspaceClient struct BreadcrumbsMenu: View { + /// The current `WorkspaceDocument` @ObservedObject var workspace: WorkspaceDocument + + /// The parent of `FileItem` for this view private var parentFileItem: WorkspaceClient.FileItem? + + /// File name private let title: String + + /// File icon private let image: String + + /// File icon's color private let color: Color init( diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift index ab4ae7fde..5e8ca9382 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift @@ -9,9 +9,11 @@ import SwiftUI import WorkspaceClient struct BreadcrumbsMenuItem: View { + /// Current `WorkspaceDocument` @ObservedObject var workspace: WorkspaceDocument + + /// The `FileItem` for this view var fileItem: WorkspaceClient.FileItem - @State var shouldLoadChildren: Bool = false var body: some View { if let children = fileItem.children?.sortItems(foldersOnTop: true), !children.isEmpty { diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index b13568c23..ccfcd51c1 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -12,45 +12,16 @@ struct BreadcrumbsView: View { @ObservedObject var workspace: WorkspaceDocument -<<<<<<< HEAD let file: WorkspaceClient.FileItem - + @State - private var projectName: String = "" - - @State private var fileItems: [WorkspaceClient.FileItem] = [] - @State - private var folders: [String] = [] - - @State - private var fileName: String = "" - - @State - private var fileImage: String = "doc" - - init(_ file: WorkspaceClient.FileItem, workspace: WorkspaceDocument) { - self.file = file - self.workspace = workspace - } - -======= - @ObservedObject var workspace: WorkspaceDocument - let file: WorkspaceClient.FileItem - - @State private var projectName: String = "" - @State private var fileItems: [WorkspaceClient.FileItem] = [] - @State private var folders: [String] = [] - @State private var fileName: String = "" - @State private var fileImage: String = "doc" - init(_ file: WorkspaceClient.FileItem, workspace: WorkspaceDocument) { self.file = file self.workspace = workspace } ->>>>>>> 5ed6842 (Adjust indent) var body: some View { ZStack(alignment: .leading) { Rectangle() @@ -61,16 +32,19 @@ struct BreadcrumbsView: View { if fileItem.parent != nil { chevron } + /// If current `fileItem` has no parent, it's the workspace root directory + /// else if current `fileItem` has no children, it's the opened file + /// else it's a folder let color = fileItem.parent == nil - ? .accentColor - : fileItem.children?.isEmpty ?? true - ? fileItem.iconColor - : .secondary + ? .accentColor + : fileItem.children?.isEmpty ?? true + ? fileItem.iconColor + : .secondary BreadcrumbsMenu(workspace, title: fileItem.fileName, systemImage: fileItem.parent == nil - ? "square.dashed.inset.filled" - : fileItem.systemImage, + ? "square.dashed.inset.filled" + : fileItem.systemImage, color: color, parentFileItem: fileItem.parent) } @@ -97,23 +71,13 @@ struct BreadcrumbsView: View { } private func fileInfo(_ file: WorkspaceClient.FileItem) { - self.fileItems = [] - var currentFile: WorkspaceClient.FileItem? = file - while currentFile != nil { - self.fileItems.insert(currentFile!, at: 0) - currentFile = currentFile!.parent - } - guard let projURL = workspace.fileURL else { return } - let components = file.url.path - .replacingOccurrences(of: projURL.path, with: "") - .split(separator: "/") - .map { String($0) } - .dropLast() - - self.projectName = projURL.lastPathComponent - self.folders = Array(components) - self.fileName = file.fileName - self.fileImage = file.systemImage + self.fileItems = [] + var currentFile: WorkspaceClient.FileItem? = file + /// Traverse from bottom to top until `currentFile` has no parent. + while currentFile != nil { + self.fileItems.insert(currentFile!, at: 0) + currentFile = currentFile!.parent + } } } diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Live.swift b/CodeEditModules/Modules/WorkspaceClient/src/Live.swift index 1119634b4..94a660fae 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Live.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Live.swift @@ -37,9 +37,9 @@ public extension WorkspaceClient { } let newFileItem = FileItem(url: itemURL, children: subItems) - subItems?.forEach { - $0.parent = newFileItem - } + subItems?.forEach { + $0.parent = newFileItem + } items.append(newFileItem) flattenedFileItems[newFileItem.id] = newFileItem } @@ -48,11 +48,11 @@ public extension WorkspaceClient { } // initial load let fileItems = try loadFiles(fromURL: folderURL) - // workspace fileItem - let workspaceItem = FileItem(url: folderURL, children: fileItems) - fileItems.forEach { item in - item.parent = workspaceItem - } + // workspace fileItem + let workspaceItem = FileItem(url: folderURL, children: fileItems) + fileItems.forEach { item in + item.parent = workspaceItem + } // By using `CurrentValueSubject` we can define a starting value. // The value passed during init it's going to be send as soon as the // consumer subscribes to the publisher. diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift index e05df6a15..4ef639a6a 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift @@ -13,8 +13,8 @@ public extension WorkspaceClient { // TODO: use a phantom type instead of a String public var id: String public var url: URL - public var children: [FileItem]? - public var parent: FileItem? + public var children: [FileItem]? + public var parent: FileItem? public static let fileManger = FileManager.default public var systemImage: String { switch children { @@ -151,9 +151,9 @@ public extension WorkspaceClient { } } - public func hash(into hasher: inout Hasher) { - hasher.combine(id) - } + public func hash(into hasher: inout Hasher) { + hasher.combine(id) + } } } From 4bdce3831d50801284b703e71b3cca0f4f3fe5b3 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Tue, 29 Mar 2022 01:12:36 +0800 Subject: [PATCH 07/11] Rebuild with NSMenu --- CodeEdit.xcodeproj/project.pbxproj | 8 -- .../Breadcrumbs/BreadcrumbsComponent.swift | 122 +++++++++++++++--- CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift | 58 --------- .../Breadcrumbs/BreadcrumbsMenuItem.swift | 37 ------ CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 17 +-- 5 files changed, 104 insertions(+), 138 deletions(-) delete mode 100644 CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift delete mode 100644 CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index fb877327e..1d8e4c58b 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,8 +54,6 @@ D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; - D76D11CA27EF191C009FE61F /* BreadcrumbsMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */; }; - D7ACE43C27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */; }; D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */; }; D7E201B227E8D50000CB86D0 /* FindNavigatorModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */; }; @@ -124,8 +122,6 @@ D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; tabWidth = 4; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; - D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenuItem.swift; sourceTree = ""; }; - D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenu.swift; sourceTree = ""; }; D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorSearchBar.swift; sourceTree = ""; }; D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorModeSelector.swift; sourceTree = ""; }; @@ -225,8 +221,6 @@ children = ( 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */, 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */, - D7ACE43B27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift */, - D76D11C927EF191C009FE61F /* BreadcrumbsMenuItem.swift */, ); path = Breadcrumbs; sourceTree = ""; @@ -564,7 +558,6 @@ B6EE989227E887C600CDD8AB /* InspectorSidebarToolbar.swift in Sources */, B673FDAD27E8296A00795864 /* PressActionsModifier.swift in Sources */, 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */, - D76D11CA27EF191C009FE61F /* BreadcrumbsMenuItem.swift in Sources */, 04660F6427E3ACAF00477777 /* Appearances.swift in Sources */, 2859B94127EB5BC00069BE88 /* TerminalSettingsView.swift in Sources */, 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */, @@ -582,7 +575,6 @@ 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */, 289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */, - D7ACE43C27ECCA2F0059FAF2 /* BreadcrumbsMenu.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */, 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */, diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index 4ed838893..12b3b1586 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -6,31 +6,115 @@ // import SwiftUI +import WorkspaceClient struct BreadcrumbsComponent: View { - @AppStorage(FileIconStyle.storageKey) - var iconStyle: FileIconStyle = .default + @AppStorage(FileIconStyle.storageKey) + var iconStyle: FileIconStyle = .default - private let title: String - private let image: String - private let color: Color + @ObservedObject + var workspace: WorkspaceDocument - init(_ title: String, systemImage image: String, color: Color = .secondary) { - self.title = title - self.image = image - self.color = color + private let fileItem: WorkspaceClient.FileItem + + @State + var position: NSPoint? + + private let menuHelper: BreadcrumbsMenuHelper + + init(_ workspace: WorkspaceDocument, fileItem: WorkspaceClient.FileItem) { + self.workspace = workspace + self.fileItem = fileItem + self.menuHelper = BreadcrumbsMenuHelper(onOpenFile: { fileItem in + workspace.openFile(item: fileItem) + }) + } + + private var image: String { + fileItem.parent == nil ? "square.dashed.inset.filled" : fileItem.systemImage + } + + /// If current `fileItem` has no parent, it's the workspace root directory + /// else if current `fileItem` has no children, it's the opened file + /// else it's a folder + private var color: Color { + fileItem.parent == nil + ? .accentColor + : fileItem.children?.isEmpty ?? true + ? fileItem.iconColor + : .secondary + } + + var body: some View { + HStack(alignment: .center) { + GeometryReader { geometry in + HStack { + Image(systemName: image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 12) + .foregroundStyle(iconStyle == .color ? color : .secondary) + .onAppear { + self.position = NSPoint( + x: geometry.frame(in: .global).minX, + y: geometry.frame(in: .global).midY + ) + } + }.frame(height: geometry.size.height) + } + Text(fileItem.fileName) + .foregroundStyle(.primary) + .font(.system(size: 11)) + .fixedSize() + .layoutPriority(1) + } + .onTapGesture { + let menu = NSMenu() + if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { + siblings.forEach { item in + let menuItem = NSMenuItem() + menuItem.title = item.fileName + var icon = item.fileIcon + var color = item.iconColor + menuItem.isEnabled = true + menuItem.target = self.menuHelper + if item.children != nil { + let subMenu = NSMenu() + menuItem.submenu = subMenu + icon = "folder.fill" + color = .secondary + } + let image = NSImage( + systemSymbolName: icon, + accessibilityDescription: icon + )?.withSymbolConfiguration(.init(paletteColors: [NSColor(color)])) + menuItem.image = image + menuItem.representedObject = item + menuItem.action = #selector(self.menuHelper.openFile) + menu.addItem(menuItem) + } + } + menu.autoenablesItems = false + if let position = position { + menu.popUp(positioning: menu.item(withTitle: fileItem.fileName), + at: position, + in: NSApp.keyWindow?.contentView) + } + } + } +} + +class BreadcrumbsMenuHelper { + + var onOpenFile: (WorkspaceClient.FileItem) -> Void + + init(onOpenFile: @escaping (WorkspaceClient.FileItem) -> Void) { + self.onOpenFile = onOpenFile } - var body: some View { - HStack { - Image(systemName: image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - .foregroundStyle(iconStyle == .color ? color : .secondary) - Text(title) - .foregroundStyle(.primary) - .font(.system(size: 11)) + @objc func openFile(_ sender: NSMenuItem) { + if let fileItem = sender.representedObject as? WorkspaceClient.FileItem { + self.onOpenFile(fileItem) } } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift deleted file mode 100644 index 79b4a3d7c..000000000 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// BreadcrumbsMenu.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2022/3/24. -// - -import SwiftUI -import WorkspaceClient - -struct BreadcrumbsMenu: View { - /// The current `WorkspaceDocument` - @ObservedObject var workspace: WorkspaceDocument - - /// The parent of `FileItem` for this view - private var parentFileItem: WorkspaceClient.FileItem? - - /// File name - private let title: String - - /// File icon - private let image: String - - /// File icon's color - private let color: Color - - init( - _ workspace: WorkspaceDocument, - title: String, - systemImage image: String, - color: Color = .secondary, - parentFileItem: WorkspaceClient.FileItem? = nil - ) { - self.workspace = workspace - self.title = title - self.image = image - self.color = color - self.parentFileItem = parentFileItem - } - - var body: some View { - // Unable to set image's color in Menu, so using this tricky way. - ZStack { - BreadcrumbsComponent(self.title, systemImage: self.image, color: self.color) - Menu { - if let siblings = parentFileItem?.children?.sortItems(foldersOnTop: true) { - ForEach(siblings, id: \.self) { item in - BreadcrumbsMenuItem(workspace: workspace, fileItem: item) - } - } - } label: { - EmptyView() - } - .menuIndicator(.hidden) - .menuStyle(.borderlessButton) - } - } -} diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift deleted file mode 100644 index 5e8ca9382..000000000 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift +++ /dev/null @@ -1,37 +0,0 @@ -// -// BreadcrumbsMenuItem.swift -// CodeEdit -// -// Created by Ziyuan Zhao on 2022/3/26. -// - -import SwiftUI -import WorkspaceClient - -struct BreadcrumbsMenuItem: View { - /// Current `WorkspaceDocument` - @ObservedObject var workspace: WorkspaceDocument - - /// The `FileItem` for this view - var fileItem: WorkspaceClient.FileItem - - var body: some View { - if let children = fileItem.children?.sortItems(foldersOnTop: true), !children.isEmpty { - // Folder - Menu { - ForEach(children, id: \.self) { child in - BreadcrumbsMenuItem(workspace: workspace, fileItem: child) - } - } label: { - BreadcrumbsComponent(fileItem.fileName, systemImage: "folder.fill", color: .secondary) - } - } else { - // File - Button { - workspace.openFile(item: fileItem) - } label: { - BreadcrumbsComponent(fileItem.fileName, systemImage: fileItem.fileIcon, color: fileItem.iconColor) - } - } - } -} diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index ccfcd51c1..8231ad059 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -32,21 +32,7 @@ struct BreadcrumbsView: View { if fileItem.parent != nil { chevron } - /// If current `fileItem` has no parent, it's the workspace root directory - /// else if current `fileItem` has no children, it's the opened file - /// else it's a folder - let color = fileItem.parent == nil - ? .accentColor - : fileItem.children?.isEmpty ?? true - ? fileItem.iconColor - : .secondary - BreadcrumbsMenu(workspace, - title: fileItem.fileName, - systemImage: fileItem.parent == nil - ? "square.dashed.inset.filled" - : fileItem.systemImage, - color: color, - parentFileItem: fileItem.parent) + BreadcrumbsComponent(workspace, fileItem: fileItem) } } .padding(.horizontal, 12) @@ -73,7 +59,6 @@ struct BreadcrumbsView: View { private func fileInfo(_ file: WorkspaceClient.FileItem) { self.fileItems = [] var currentFile: WorkspaceClient.FileItem? = file - /// Traverse from bottom to top until `currentFile` has no parent. while currentFile != nil { self.fileItems.insert(currentFile!, at: 0) currentFile = currentFile!.parent From f064423602e1c8094350cc08e9bedbd98e8061d2 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Tue, 29 Mar 2022 16:39:35 +0800 Subject: [PATCH 08/11] Lazy load submenu --- CodeEdit.xcodeproj/project.pbxproj | 4 + .../Breadcrumbs/BreadcrumbsComponent.swift | 33 ++------ .../Breadcrumbs/BreadcrumbsMenuItem.swift | 82 +++++++++++++++++++ 3 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 1d8e4c58b..0d55b0506 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,6 +54,7 @@ D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; + D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */; }; D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */; }; D7E201B227E8D50000CB86D0 /* FindNavigatorModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */; }; @@ -122,6 +123,7 @@ D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; tabWidth = 4; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; + D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenuItem.swift; sourceTree = ""; }; D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorSearchBar.swift; sourceTree = ""; }; D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorModeSelector.swift; sourceTree = ""; }; @@ -221,6 +223,7 @@ children = ( 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */, 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */, + D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */, ); path = Breadcrumbs; sourceTree = ""; @@ -574,6 +577,7 @@ D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */, 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */, + D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift in Sources */, 289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */, diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index 12b3b1586..1f6fd27cd 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -69,37 +69,14 @@ struct BreadcrumbsComponent: View { .layoutPriority(1) } .onTapGesture { - let menu = NSMenu() if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { - siblings.forEach { item in - let menuItem = NSMenuItem() - menuItem.title = item.fileName - var icon = item.fileIcon - var color = item.iconColor - menuItem.isEnabled = true - menuItem.target = self.menuHelper - if item.children != nil { - let subMenu = NSMenu() - menuItem.submenu = subMenu - icon = "folder.fill" - color = .secondary - } - let image = NSImage( - systemSymbolName: icon, - accessibilityDescription: icon - )?.withSymbolConfiguration(.init(paletteColors: [NSColor(color)])) - menuItem.image = image - menuItem.representedObject = item - menuItem.action = #selector(self.menuHelper.openFile) - menu.addItem(menuItem) + let menu = BreadcrumsMenu(siblings, workspace: workspace) + if let position = position { + menu.popUp(positioning: menu.item(withTitle: fileItem.fileName), + at: position, + in: NSApp.keyWindow?.contentView) } } - menu.autoenablesItems = false - if let position = position { - menu.popUp(positioning: menu.item(withTitle: fileItem.fileName), - at: position, - in: NSApp.keyWindow?.contentView) - } } } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift new file mode 100644 index 000000000..90c16ade9 --- /dev/null +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift @@ -0,0 +1,82 @@ +// +// BreadcrumbsMenuItem.swift +// CodeEdit +// +// Created by Ziyuan Zhao on 2022/3/29. +// + +import AppKit +import WorkspaceClient + +class BreadcrumsMenu: NSMenu, NSMenuDelegate { + + let fileItems: [WorkspaceClient.FileItem] + let workspace: WorkspaceDocument + + init(_ fileItems: [WorkspaceClient.FileItem], workspace: WorkspaceDocument) { + self.fileItems = fileItems + self.workspace = workspace + super.init(title: "") + self.delegate = self + fileItems.forEach { item in + let menuItem = BreadcrumbsMenuItem(item, workspace: workspace) + self.addItem(menuItem) + } + self.autoenablesItems = false + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) { + if let highlightedItem = item, let submenuItems = highlightedItem.submenu?.items, submenuItems.isEmpty { + if let highlightedFileItem = highlightedItem.representedObject as? WorkspaceClient.FileItem { + highlightedItem.submenu = generateSubmenu(highlightedFileItem) + } + } + } + + private func generateSubmenu(_ fileItem: WorkspaceClient.FileItem) -> BreadcrumsMenu? { + if let children = fileItem.children { + let menu = BreadcrumsMenu(children, workspace: workspace) + return menu + } + return nil + } +} + +class BreadcrumbsMenuItem: NSMenuItem { + let fileItem: WorkspaceClient.FileItem + var workspace: WorkspaceDocument + + init(_ fileItem: WorkspaceClient.FileItem, workspace: WorkspaceDocument) { + self.fileItem = fileItem + self.workspace = workspace + super.init(title: fileItem.fileName, action: #selector(openFile), keyEquivalent: "") + var icon = fileItem.fileIcon + var color = fileItem.iconColor + self.isEnabled = true + self.target = self + if fileItem.children != nil { + let subMenu = NSMenu() + self.submenu = subMenu + icon = "folder.fill" + color = .secondary + } + let image = NSImage( + systemSymbolName: icon, + accessibilityDescription: icon + )?.withSymbolConfiguration(.init(paletteColors: [NSColor(color)])) + self.image = image + self.representedObject = fileItem + } + + required init(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc func openFile() { + workspace.openFile(item: fileItem) + } +} From 43258904197b61471692fb17abcf1fc3497b9594 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Tue, 29 Mar 2022 20:40:12 +0800 Subject: [PATCH 09/11] Cleanup code and fix close crash --- CodeEdit.xcodeproj/project.pbxproj | 8 +++---- .../Breadcrumbs/BreadcrumbsComponent.swift | 23 ++----------------- ...bsMenuItem.swift => BreadcrumbsMenu.swift} | 3 ++- .../WorkspaceClient/src/Model/FileItem.swift | 21 +++++++++++++++++ 4 files changed, 29 insertions(+), 26 deletions(-) rename CodeEdit/Breadcrumbs/{BreadcrumbsMenuItem.swift => BreadcrumbsMenu.swift} (96%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 0d55b0506..f8b21c1f8 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -54,7 +54,7 @@ D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */; }; D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */; }; D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */; }; - D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */; }; + D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenu.swift */; }; D7E201AE27E8B3C000CB86D0 /* String+Ranges.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */; }; D7E201B027E8C07300CB86D0 /* FindNavigatorSearchBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */; }; D7E201B227E8D50000CB86D0 /* FindNavigatorModeSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */; }; @@ -123,7 +123,7 @@ D72E1A8227E3B0D400EB11B9 /* WelcomeView.swift */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = ""; tabWidth = 4; }; D72E1A8627E4242900EB11B9 /* RecentProjectsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentProjectsView.swift; sourceTree = ""; }; D72E1A8827E44D7C00EB11B9 /* WelcomeWindowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeWindowView.swift; sourceTree = ""; }; - D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenuItem.swift; sourceTree = ""; }; + D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsMenu.swift; sourceTree = ""; }; D7E201AD27E8B3C000CB86D0 /* String+Ranges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Ranges.swift"; sourceTree = ""; }; D7E201AF27E8C07300CB86D0 /* FindNavigatorSearchBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorSearchBar.swift; sourceTree = ""; }; D7E201B127E8D50000CB86D0 /* FindNavigatorModeSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorModeSelector.swift; sourceTree = ""; }; @@ -223,7 +223,7 @@ children = ( 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */, 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */, - D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift */, + D76D11CB27F2F2B6009FE61F /* BreadcrumbsMenu.swift */, ); path = Breadcrumbs; sourceTree = ""; @@ -577,7 +577,7 @@ D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */, 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */, B6EE989027E8879A00CDD8AB /* InspectorSidebar.swift in Sources */, - D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenuItem.swift in Sources */, + D76D11CC27F2F2B6009FE61F /* BreadcrumbsMenu.swift in Sources */, 289978ED27E4E97E00BB0357 /* FileIconStyle.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, D72E1A8327E3B0D400EB11B9 /* WelcomeView.swift in Sources */, diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index 1f6fd27cd..891493c2a 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -19,15 +19,10 @@ struct BreadcrumbsComponent: View { @State var position: NSPoint? - - private let menuHelper: BreadcrumbsMenuHelper init(_ workspace: WorkspaceDocument, fileItem: WorkspaceClient.FileItem) { self.workspace = workspace self.fileItem = fileItem - self.menuHelper = BreadcrumbsMenuHelper(onOpenFile: { fileItem in - workspace.openFile(item: fileItem) - }) } private var image: String { @@ -47,6 +42,8 @@ struct BreadcrumbsComponent: View { var body: some View { HStack(alignment: .center) { + /// Get location in window + /// Can't use it outside `HStack` becuase it'll make the whole `BreadcrumsComponent` flexiable. GeometryReader { geometry in HStack { Image(systemName: image) @@ -66,7 +63,6 @@ struct BreadcrumbsComponent: View { .foregroundStyle(.primary) .font(.system(size: 11)) .fixedSize() - .layoutPriority(1) } .onTapGesture { if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { @@ -80,18 +76,3 @@ struct BreadcrumbsComponent: View { } } } - -class BreadcrumbsMenuHelper { - - var onOpenFile: (WorkspaceClient.FileItem) -> Void - - init(onOpenFile: @escaping (WorkspaceClient.FileItem) -> Void) { - self.onOpenFile = onOpenFile - } - - @objc func openFile(_ sender: NSMenuItem) { - if let fileItem = sender.representedObject as? WorkspaceClient.FileItem { - self.onOpenFile(fileItem) - } - } -} diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift similarity index 96% rename from CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift rename to CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift index 90c16ade9..75e2f2845 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsMenuItem.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsMenu.swift @@ -1,5 +1,5 @@ // -// BreadcrumbsMenuItem.swift +// BreadcrumbsMenu.swift // CodeEdit // // Created by Ziyuan Zhao on 2022/3/29. @@ -29,6 +29,7 @@ class BreadcrumsMenu: NSMenu, NSMenuDelegate { fatalError("init(coder:) has not been implemented") } + /// Only when menu item is highlighted then generate its submenu func menu(_ menu: NSMenu, willHighlight item: NSMenuItem?) { if let highlightedItem = item, let submenuItems = highlightedItem.submenu?.items, submenuItems.isEmpty { if let highlightedFileItem = highlightedItem.representedObject as? WorkspaceClient.FileItem { diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift index 4ef639a6a..b410b8e00 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift @@ -9,6 +9,13 @@ import Foundation import SwiftUI public extension WorkspaceClient { + enum FileItemCodingKeys: String, CodingKey { + case id + case url + case children + case parent + } + class FileItem: Hashable, Identifiable, Comparable, Codable { // TODO: use a phantom type instead of a String public var id: String @@ -154,6 +161,20 @@ public extension WorkspaceClient { public func hash(into hasher: inout Hasher) { hasher.combine(id) } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: FileItemCodingKeys.self) + try container.encode(id, forKey: .id) + try container.encode(url, forKey: .url) + try container.encode(children, forKey: .children) + } + + public required init(from decoder: Decoder) throws { + let values = try decoder.container(keyedBy: FileItemCodingKeys.self) + id = try values.decode(String.self, forKey: .id) + url = try values.decode(URL.self, forKey: .url) + children = try values.decode([FileItem].self, forKey: .children) + } } } From caae42cfe8a5e519d3b42a0663d2db95b33f364a Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Tue, 29 Mar 2022 21:11:45 +0800 Subject: [PATCH 10/11] Fix opened files --- CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift | 1 - CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 6 +++--- CodeEdit/Documents/WorkspaceDocument.swift | 4 +++- .../Modules/WorkspaceClient/src/Model/FileItem.swift | 7 +++++-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index 891493c2a..a947f5e43 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -62,7 +62,6 @@ struct BreadcrumbsComponent: View { Text(fileItem.fileName) .foregroundStyle(.primary) .font(.system(size: 11)) - .fixedSize() } .onTapGesture { if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index 8231ad059..40989ddce 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -59,9 +59,9 @@ struct BreadcrumbsView: View { private func fileInfo(_ file: WorkspaceClient.FileItem) { self.fileItems = [] var currentFile: WorkspaceClient.FileItem? = file - while currentFile != nil { - self.fileItems.insert(currentFile!, at: 0) - currentFile = currentFile!.parent + while let currentFileLoop = currentFile { + self.fileItems.insert(currentFileLoop, at: 0) + currentFile = currentFileLoop.parent } } } diff --git a/CodeEdit/Documents/WorkspaceDocument.swift b/CodeEdit/Documents/WorkspaceDocument.swift index 8ca68bea4..0e7278989 100644 --- a/CodeEdit/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Documents/WorkspaceDocument.swift @@ -138,7 +138,9 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { let state = try JSONDecoder().decode(WorkspaceSelectionState.self, from: Data(contentsOf: selectionStateFile)) self.selectionState.fileItems = state.fileItems - state.openFileItems.forEach { item in + state.openFileItems + .compactMap { try? workspaceClient?.getFileItem($0.id) } + .forEach { item in self.openFile(item: item) } self.selectionState.selectedId = state.selectedId diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift index b410b8e00..67e746c44 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift @@ -13,7 +13,6 @@ public extension WorkspaceClient { case id case url case children - case parent } class FileItem: Hashable, Identifiable, Comparable, Codable { @@ -158,10 +157,14 @@ public extension WorkspaceClient { } } + // MARK: Hashable public func hash(into hasher: inout Hasher) { hasher.combine(id) + hasher.combine(url) + hasher.combine(children) } + // MARK: Codable public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: FileItemCodingKeys.self) try container.encode(id, forKey: .id) @@ -173,7 +176,7 @@ public extension WorkspaceClient { let values = try decoder.container(keyedBy: FileItemCodingKeys.self) id = try values.decode(String.self, forKey: .id) url = try values.decode(URL.self, forKey: .url) - children = try values.decode([FileItem].self, forKey: .children) + children = try values.decode([FileItem]?.self, forKey: .children) } } } From b5853ff495a3a8b3ba0cd74db932dc5b0cb0e232 Mon Sep 17 00:00:00 2001 From: Ziyuan Zhao Date: Tue, 29 Mar 2022 22:37:48 +0800 Subject: [PATCH 11/11] Fix conflict and lint --- .../Breadcrumbs/BreadcrumbsComponent.swift | 18 +++++++++--------- CodeEdit/Breadcrumbs/BreadcrumbsView.swift | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift index a947f5e43..a3d61d083 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsComponent.swift @@ -9,16 +9,16 @@ import SwiftUI import WorkspaceClient struct BreadcrumbsComponent: View { - @AppStorage(FileIconStyle.storageKey) - var iconStyle: FileIconStyle = .default + @AppStorage(FileIconStyle.storageKey) + var iconStyle: FileIconStyle = .default @ObservedObject - var workspace: WorkspaceDocument + var workspace: WorkspaceDocument private let fileItem: WorkspaceClient.FileItem @State - var position: NSPoint? + var position: NSPoint? init(_ workspace: WorkspaceDocument, fileItem: WorkspaceClient.FileItem) { self.workspace = workspace @@ -40,7 +40,7 @@ struct BreadcrumbsComponent: View { : .secondary } - var body: some View { + var body: some View { HStack(alignment: .center) { /// Get location in window /// Can't use it outside `HStack` becuase it'll make the whole `BreadcrumsComponent` flexiable. @@ -60,9 +60,9 @@ struct BreadcrumbsComponent: View { }.frame(height: geometry.size.height) } Text(fileItem.fileName) - .foregroundStyle(.primary) - .font(.system(size: 11)) - } + .foregroundStyle(.primary) + .font(.system(size: 11)) + } .onTapGesture { if let siblings = fileItem.parent?.children?.sortItems(foldersOnTop: true), !siblings.isEmpty { let menu = BreadcrumsMenu(siblings, workspace: workspace) @@ -73,5 +73,5 @@ struct BreadcrumbsComponent: View { } } } - } + } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index 40989ddce..4190d596b 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -13,9 +13,9 @@ struct BreadcrumbsView: View { var workspace: WorkspaceDocument let file: WorkspaceClient.FileItem - + @State - private var fileItems: [WorkspaceClient.FileItem] = [] + private var fileItems: [WorkspaceClient.FileItem] = [] init(_ file: WorkspaceClient.FileItem, workspace: WorkspaceDocument) { self.file = file