import SwiftUI import SaplingCore public enum WorkspaceTreeSelection: Hashable, Sendable { case folder(URL) case file(URL) case project(URL) case subproject(URL) } public struct WorkspaceTreeView: View { private let workspace: Workspace? @Binding private var selection: WorkspaceTreeSelection? private let onSelectFile: (WorkspaceFile) -> Void private let onSelectProject: (Project) -> Void private let onOpenWorkspace: () -> Void public init( workspace: Workspace?, selection: Binding, onSelectFile: @escaping (WorkspaceFile) -> Void, onSelectProject: @escaping (Project) -> Void, onOpenWorkspace: @escaping () -> Void ) { self.workspace = workspace self._selection = selection self.onSelectFile = onSelectFile self.onSelectProject = onSelectProject self.onOpenWorkspace = onOpenWorkspace } public var body: some View { Group { if let workspace { List(selection: $selection) { Section(workspace.name) { ForEach(workspace.items) { item in WorkspaceItemRow( item: item, selection: $selection, onSelectFile: onSelectFile, onSelectProject: onSelectProject ) } } } .listStyle(.sidebar) } else { ContentUnavailableView { Label("No Workspace", systemImage: "folder") } description: { Text("Choose a folder to browse Markdown files.") } actions: { Button("Open Workspace...", action: onOpenWorkspace) } } } .navigationTitle("Workspace") } } private struct WorkspaceItemRow: View { let item: WorkspaceItem @Binding var selection: WorkspaceTreeSelection? let onSelectFile: (WorkspaceFile) -> Void let onSelectProject: (Project) -> Void var body: some View { switch item { case .folder(let folder): DisclosureGroup { ForEach(folder.children) { child in WorkspaceItemRow( item: child, selection: $selection, onSelectFile: onSelectFile, onSelectProject: onSelectProject ) } } label: { Label(folder.name, systemImage: "folder") .contentShape(Rectangle()) .onTapGesture { selection = .folder(folder.url) } } .tag(WorkspaceTreeSelection.folder(folder.url)) case .file(let file): Label(file.name, systemImage: iconName(for: file.kind)) .foregroundStyle(file.kind == .markdown ? .primary : .secondary) .contentShape(Rectangle()) .onTapGesture { selection = .file(file.url) onSelectFile(file) } .tag(WorkspaceTreeSelection.file(file.url)) case .project(let project): DisclosureGroup { ForEach(project.children) { child in WorkspaceItemRow( item: child, selection: $selection, onSelectFile: onSelectFile, onSelectProject: onSelectProject ) } } label: { Label(project.name, systemImage: "leaf") .fontWeight(.medium) .foregroundStyle(.primary) .contentShape(Rectangle()) .onTapGesture { selection = .project(project.repositoryURL) onSelectProject(project) } } .tag(WorkspaceTreeSelection.project(project.repositoryURL)) case .subproject(let subproject): Label(subproject.name, systemImage: "rectangle.connected.to.line.below") .foregroundStyle(.secondary) .tag(WorkspaceTreeSelection.subproject(subproject.repositoryURL)) } } private func iconName(for kind: WorkspaceFileKind) -> String { switch kind { case .markdown: "doc.plaintext" case .attachment: "paperclip" case .other: "doc" } } }