import SwiftUI import UIKit struct GitAwareFileNavigatorView: View { let tree: FileNode? let fileStatuses: [GitFileStatus] let onSelect: (FileNode) -> Void var onCreateFile: ((String) -> Void)? = nil var onCreateFolder: ((String) -> Void)? = nil var onRename: ((FileNode) -> Void)? = nil var onDelete: ((FileNode) -> Void)? = nil private var statusMap: [String: GitFileStatus] { Dictionary(uniqueKeysWithValues: fileStatuses.map { ($0.path, $0) }) } var body: some View { Group { if let tree { List { GitAwareNodeRow(node: tree, statusMap: statusMap, depth: 0, onSelect: onSelect, onCreateFile: onCreateFile, onCreateFolder: onCreateFolder, onRename: onRename, onDelete: onDelete) } .listStyle(.sidebar) } else { ContentUnavailableView("No Project Loaded", systemImage: "folder.badge.questionmark", description: Text("Check your daemon connection.")) } } } } struct GitAwareNodeRow: View { let node: FileNode let statusMap: [String: GitFileStatus] let depth: Int let onSelect: (FileNode) -> Void var onCreateFile: ((String) -> Void)? var onCreateFolder: ((String) -> Void)? var onRename: ((FileNode) -> Void)? var onDelete: ((FileNode) -> Void)? @State private var isExpanded = true @State private var showDeleteAlert = false private var fileStatus: GitFileStatus? { statusMap.values.first { node.path.hasSuffix($0.path) } } var body: some View { if node.isDirectory { DisclosureGroup(isExpanded: $isExpanded) { ForEach(node.children ?? []) { child in GitAwareNodeRow(node: child, statusMap: statusMap, depth: depth + 1, onSelect: onSelect, onCreateFile: onCreateFile, onCreateFolder: onCreateFolder, onRename: onRename, onDelete: onDelete) } } label: { Label(node.name, systemImage: isExpanded ? "folder.fill" : "folder") .font(.body) } .contextMenu { directoryContextMenu } } else { Button { onSelect(node) } label: { HStack(spacing: 6) { Image(systemName: fileIcon(for: node.name)) .font(.system(size: 12)).foregroundStyle(iconColor(for: node.name)).frame(width: 16) Text(node.name) .font(.system(.body, design: .monospaced)) .foregroundStyle(fileStatus == nil ? .primary : fileStatus!.statusSwiftUIColor) Spacer() if let s = fileStatus { Text(badge(s.status)) .font(.system(.caption2, design: .monospaced).bold()) .foregroundStyle(s.statusSwiftUIColor) } } .contentShape(Rectangle()) } .buttonStyle(.plain) .contextMenu { fileContextMenu } .alert("Delete \"\(node.name)\"?", isPresented: $showDeleteAlert) { Button("Delete", role: .destructive) { onDelete?(node) } Button("Cancel", role: .cancel) {} } } } private func badge(_ status: String) -> String { switch status { case "modified": return "M"; case "added": return "A" case "deleted": return "D"; case "untracked": return "?"; default: return "" } } private func fileIcon(for name: String) -> String { switch (name as NSString).pathExtension.lowercased() { case "swift": return "swift"; case "json": return "curlybraces" case "md": return "doc.text"; case "sh": return "terminal" case "xcconfig": return "gearshape"; default: return "doc" } } private func iconColor(for name: String) -> Color { switch (name as NSString).pathExtension.lowercased() { case "swift": return .orange; case "json": return .yellow case "md": return .blue; default: return .secondary } } @ViewBuilder private var directoryContextMenu: some View { Button { onCreateFile?(node.path) } label: { Label("New File", systemImage: "doc.badge.plus") } Button { onCreateFolder?(node.path) } label: { Label("New Folder", systemImage: "folder.badge.plus") } Divider() Button { onRename?(node) } label: { Label("Rename…", systemImage: "pencil") } Divider() Button(role: .destructive) { showDeleteAlert = true } label: { Label("Delete", systemImage: "trash") } } @ViewBuilder private var fileContextMenu: some View { Button { onSelect(node) } label: { Label("Open", systemImage: "arrow.up.right.square") } Button { onRename?(node) } label: { Label("Rename…", systemImage: "pencil") } Divider() Button(role: .destructive) { showDeleteAlert = true } label: { Label("Delete", systemImage: "trash") } } }