From 925979a7826c7feca8e3079aa4a54d22beefa2ec Mon Sep 17 00:00:00 2001 From: Luke Date: Sat, 26 Mar 2022 02:59:40 +0100 Subject: [PATCH] UI adjustments in ProjectNavigator, added docs, moved context menu to separate view --- CodeEdit.xcodeproj/project.pbxproj | 4 + .../ProjectNavigator/ProjectNavigator.swift | 39 +++-- .../ProjectNavigatorContextMenu.swift | 145 ++++++++++++++++++ .../ProjectNavigatorItem.swift | 121 +++------------ 4 files changed, 197 insertions(+), 112 deletions(-) create mode 100644 CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorContextMenu.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 388b508d50..ab5d3000b6 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */; }; 0485EB2327E7791400138301 /* QuickOpenPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */; }; 0485EB2527E7B9C800138301 /* Overlays in Frameworks */ = {isa = PBXBuildFile; productRef = 0485EB2427E7B9C800138301 /* Overlays */; }; + 2806CBD427EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */; }; 2813F93827ECC4AA00E305E4 /* FindNavigatorResultList.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E201B327E9989900CB86D0 /* FindNavigatorResultList.swift */; }; 2813F93927ECC4C300E305E4 /* LeadingSidebar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E627E3413200D46668 /* LeadingSidebar.swift */; }; 2813F93A27ECC4CC00E305E4 /* ProjectNavigatorItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776EC27E350D800D46668 /* ProjectNavigatorItem.swift */; }; @@ -97,6 +98,7 @@ 12F71F7127E833A000095416 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Localizable.strings; sourceTree = ""; }; 20F8067027E65A5200EB7827 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/Localizable.strings; sourceTree = ""; }; 2805D9E827E5ED180032BC56 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; + 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorContextMenu.swift; sourceTree = ""; }; 2859B94027EB5BC00069BE88 /* TerminalSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalSettingsView.swift; sourceTree = ""; }; 286471AA27ED51FD0039369D /* ProjectNavigator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigator.swift; sourceTree = ""; }; 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbsComponent.swift; sourceTree = ""; }; @@ -221,6 +223,7 @@ children = ( 286471AA27ED51FD0039369D /* ProjectNavigator.swift */, 287776EC27E350D800D46668 /* ProjectNavigatorItem.swift */, + 2806CBD327EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift */, ); path = ProjectNavigator; sourceTree = ""; @@ -562,6 +565,7 @@ buildActionMask = 2147483647; files = ( 2813F93A27ECC4CC00E305E4 /* ProjectNavigatorItem.swift in Sources */, + 2806CBD427EE9FC0003E5523 /* ProjectNavigatorContextMenu.swift in Sources */, 2813F93927ECC4C300E305E4 /* LeadingSidebar.swift in Sources */, 2813F93827ECC4AA00E305E4 /* FindNavigatorResultList.swift in Sources */, 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */, diff --git a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigator.swift b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigator.swift index 6008935e1a..98064181e1 100644 --- a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigator.swift +++ b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigator.swift @@ -8,29 +8,37 @@ import SwiftUI import WorkspaceClient +/// # Project Navigator - Sidebar +/// +/// A list that functions as a project navigator, showing collapsable folders +/// and files. +/// +/// When selecting a file it will open in the editor. +/// struct ProjectNavigator: View { @ObservedObject var workspace: WorkspaceDocument var windowController: NSWindowController + /// The `ID` of the currently selected file/folder. If none is selected this is `nil` @State private var selection: WorkspaceClient.FileItem.ID? var body: some View { List(selection: $selection) { - Text(workspace.workspaceClient?.folderURL()?.lastPathComponent ?? "Empty") - .font(.callout.bold()) - .foregroundColor(.secondary) - .padding(.bottom, 4) - ForEach(workspace.selectionState.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop)) { item in - ProjectNavigatorItem( - item: item, - workspace: workspace, - windowController: windowController, - shouldloadChildren: .constant(true), // First level of children should always be loaded - selectedId: $selection - ) + Section { + ForEach(workspace.selectionState.fileItems.sortItems(foldersOnTop: workspace.sortFoldersOnTop)) { item in + ProjectNavigatorItem( + item: item, + workspace: workspace, + windowController: windowController, + shouldloadChildren: .constant(true), // First level of children should always be loaded + selectedId: $selection + ) + } + } header: { + Text(projectName) + .padding(.vertical, 8) } } - .environment(\.defaultMinListRowHeight, 8) .listRowInsets(.init()) .onChange(of: selection) { newValue in guard let id = newValue, @@ -43,4 +51,9 @@ struct ProjectNavigator: View { selection = newValue } } + + /// The name of the project (name of the selected top-level folder) + private var projectName: String { + workspace.workspaceClient?.folderURL()?.lastPathComponent ?? "Project" + } } diff --git a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorContextMenu.swift b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorContextMenu.swift new file mode 100644 index 0000000000..a4c48d6e77 --- /dev/null +++ b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorContextMenu.swift @@ -0,0 +1,145 @@ +// +// ProjectNavigatorContextMenu.swift +// CodeEdit +// +// Created by Lukas Pistrol on 26.03.22. +// + +import SwiftUI +import WorkspaceClient + +// TODO: Some implementations still need to be done +/// # Project Navigator Context Menu +/// +/// A context menu which shows when right clicking on a folder or file +/// in the ``ProjectNavigator``. +/// +struct ProjectNavigatorContextMenu: View { + + private var item: WorkspaceClient.FileItem + private var isFolder: Bool + + init(_ item: WorkspaceClient.FileItem, isFolder: Bool) { + self.item = item + self.isFolder = isFolder + } + + @ViewBuilder + var body: some View { + showInFinder + openSection + showFileInspector + addSection + delete + newFolderSection + sortSection + findInSelectedGroups + sourceControl + } + + private var showInFinder: some View { + Group { + Button("Show in Finder") { + item.showInFinder() + } + Divider() + } + } + + private var openSection: some View { + Group { + Button("Open in Tab") { + // Open a new tab + } + Button("Open in New Window") { + // Open a new window + } + Divider() + } + } + + private var showFileInspector: some View { + Group { + Button("Show File Inspector") { + // Show File Inspector + } + Divider() + } + } + + private var addSection: some View { + Group { + Button("New File") { + item.addFile(fileName: "randomFile.txt") + } + Button("Add files to folder") { + // Add Files to Folder + } + Divider() + } + } + + private var delete: some View { + Group { + Button("Delete") { + item.delete() + } + Divider() + } + } + + private var newFolderSection: some View { + Group { + Button("New Folder") { + item.addFolder(folderName: "Test Folder") + } + Button("New Folder from Selection") { + // New Group from Selection + } + Divider() + } + } + + private var sortSection: some View { + Group { + Button("Sort by Name") { + // Sort folder items by name + } + .disabled(!isFolder) + Button("Sort by Type") { + // Sort folder items by file type + } + .disabled(!isFolder) + Divider() + } + } + + private var findInSelectedGroups: some View { + Group { + Button("Find in Selected Groups...") { + // Find in selected groups... + } + .disabled(!isFolder) + Divider() + } + } + + private var sourceControl: some View { + Menu("Source Control") { + Button("Commit Selected File") { + // Commit selected file + } + Divider() + Button("Discard Changes in Selected File") { + // Discard changes made to the selected file + } + Divider() + Button("Add") { + // Add file to git + } + Button("Mark as Resolved") { + // Mark file as resolved + } + } + } +} diff --git a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift index 0cfd6990a3..0bd4a4cae2 100644 --- a/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift +++ b/CodeEdit/NavigatorSidebar/ProjectNavigator/ProjectNavigatorItem.swift @@ -10,9 +10,11 @@ import SwiftUI import WorkspaceClient import CodeFile +/// # Project Navigator Item +/// +/// Displays a File or Folder in the ``ProjectNavigator``. +/// struct ProjectNavigatorItem: View { - @Environment(\.controlActiveState) var activeState - @Environment(\.colorScheme) var colorScheme @AppStorage(FileIconStyle.storageKey) var iconStyle: FileIconStyle = .default /// The `FileItem` for this view @@ -48,27 +50,22 @@ struct ProjectNavigatorItem: View { } } - func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View { - HStack { - Image(systemName: item.systemImage) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12, height: 12, alignment: .center) - .foregroundColor(iconColor) - .opacity(activeState == .inactive ? 0.45 : 1) - Text(item.url.lastPathComponent) - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - .listRowInsets(.init()) - .contextMenu { contextMenuContent(false) } + /// A `Label` representing a file + private func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View { + Label(item.url.lastPathComponent, systemImage: item.systemImage) + .accentColor(iconColor) + .contextMenu { + ProjectNavigatorContextMenu(item, isFolder: false) + } } + /// A `DisclosureGroup` representing a folder. + /// + /// When expanded, it shows files/folders inside @ViewBuilder - func sidebarFolderItem(_ item: WorkspaceClient.FileItem) -> some View { + private func sidebarFolderItem(_ item: WorkspaceClient.FileItem) -> some View { DisclosureGroup(isExpanded: $isExpanded) { - if shouldloadChildren { // Only load when expanded -> Improves performance massively + if shouldloadChildren { // Only load when parent is expanded -> Improves performance massively ForEach(item.children!.sortItems(foldersOnTop: workspace.sortFoldersOnTop)) { child in ProjectNavigatorItem( item: child, @@ -80,90 +77,16 @@ struct ProjectNavigatorItem: View { } } } label: { - HStack { - Image(systemName: item.systemImage) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12, height: 12, alignment: .center) - .foregroundColor(.secondary) - .opacity(activeState == .inactive ? 0.45 : 1) - Text(item.url.lastPathComponent) - .font(.subheadline) - .frame(maxWidth: .infinity, alignment: .leading) - .contentShape(Rectangle()) - } - .contextMenu { contextMenuContent(true) } + Label(item.url.lastPathComponent, systemImage: item.systemImage) + .accentColor(.secondary) + .contextMenu { + ProjectNavigatorContextMenu(item, isFolder: true) + } } } + /// Returns a color depending on the set icon color style in preferences private var iconColor: Color { return iconStyle == .color ? item.iconColor : .secondary } - - // TODO: Some implementations still need to be done - /// maximum number of views in a container is exceeded (in SwiftUI). The max = 10 - @ViewBuilder - private func contextMenuContent(_ isFolder: Bool) -> some View { - Button("Show in Finder") { item.showInFinder() } - Group { - Divider() - Button("Open in Tab") { - // Open a new tab - } - Button("Open in New Window") { - // Open a new window - } - Divider() - } - Button("Show File Inspector") { /* Show File Inspector */ } - Group { - Divider() - Button("New File") { - item.addFile(fileName: "randomFile.txt") - } - Button("Add files to folder") { - // Add Files to Folder - } - Divider() - } - Button("Delete") { item.delete() } - Group { - Divider() - Button("New Folder") { - item.addFolder(folderName: "Test Folder") - } - Button("New Folder from Selection") { - // New Group from Selection - } - Divider() - } - Group { - Button("Sort by Name") { - // Sort folder items by name - }.disabled(isFolder ? false : true) - Button("Sort by Type") { - // Sort folder items by file type - }.disabled(isFolder ? false : true) - Divider() - } - Button("Find in Selected Groups...") {} - .disabled(isFolder ? false : true) - Divider() - Menu("Source Control") { - Button("Commit Selected File") { - // Commit selected file - } - Divider() - Button("Discard Changes in Selected File") { - // Discard changes made to the selected file - } - Divider() - Button("Add") { - // Add file to git - } - Button("Mark as Resolved") { - // Mark file as resolved - } - } - } }