import SwiftUI import UniformTypeIdentifiers enum WorkspaceTool: String, CaseIterable, Identifiable { case media = "影视库" case scan = "文件扫描与清理" case rename = "批量重命名" case tmdb = "TMDB 整理" case settings = "工作区设置" var id: String { rawValue } var icon: String { switch self { case .media: return "play.tv.fill" case .scan: return "magnifyingglass.circle.fill" case .rename: return "character.cursor.ibeam" case .tmdb: return "film.stack" case .settings: return "gearshape" } } var subtitle: String { switch self { case .media: return "海报墙、媒体详情与播放入口" case .scan: return "空文件夹、重复文件与相似文件夹统一扫描" case .rename: return "按字符、正则和组合规则批量修改名称" case .tmdb: return "配置并执行媒体识别整理" case .settings: return "代理和本机工具配置" } } } struct WorkspaceView: View { @ObservedObject var model: AppModel @State private var selectedFileIDs: Set = [] @State private var renameFile: CloudFile? @State private var isCreatingFolder = false @State private var isImporting = false @State private var isShowingSettings = false @State private var isShowingDuplicates = false @State private var isInspectorPresented = false @State private var selectedTool: WorkspaceTool? @State private var folderName = "" @State private var columnVisibility: NavigationSplitViewVisibility = .all @State private var previewFile: CloudFile? @State private var globalSearchText = "" private var selectedFile: CloudFile? { selectedFileIDs.count == 1 ? model.files.first { $0.id == selectedFileIDs.first } : nil } var body: some View { ZStack { AppBackdrop() NavigationSplitView(columnVisibility: $columnVisibility) { WorkspaceSidebar(model: model, selectedTool: $selectedTool, columnVisibility: $columnVisibility) .navigationSplitViewColumnWidth(min: 210, ideal: 238, max: 280) } detail: { if let selectedTool { ToolWorkspacePage(model: model, tool: selectedTool) } else { FilesBrowser( model: model, selectedFileIDs: $selectedFileIDs, globalSearchText: $globalSearchText, renameFile: $renameFile, isCreatingFolder: $isCreatingFolder, isImporting: $isImporting, isShowingSettings: $isShowingSettings, isShowingDuplicates: $isShowingDuplicates, onPreview: { previewFile = $0 }, onOpenTool: { selectedTool = $0 }, onShowInspector: { isInspectorPresented = true } ) .frame(minWidth: 680, maxWidth: .infinity, maxHeight: .infinity) .inspector(isPresented: $isInspectorPresented) { FileInspector( model: model, file: selectedFile, onPreview: { previewFile = $0 }, onClose: { isInspectorPresented = false } ) .inspectorColumnWidth(min: 280, ideal: 320, max: 420) } } } .scrollContentBackground(.hidden) .toolbar { ToolbarItem(placement: .principal) { AppTitleToolbar(model: model, selectedTool: selectedTool, searchText: $globalSearchText) } ToolbarItem(placement: .primaryAction) { if selectedTool == nil { Button { isInspectorPresented.toggle() } label: { Image(systemName: "sidebar.trailing") } .help(isInspectorPresented ? "隐藏详情检查器" : "显示详情检查器") .accessibilityLabel(isInspectorPresented ? "隐藏详情检查器" : "显示详情检查器") .keyboardShortcut("i", modifiers: [.command, .option]) .disabled(selectedFile == nil) } } } } .frame(minWidth: 1040, minHeight: 700) .sheet(item: $renameFile) { file in RenameSheet(file: file) { name in Task { await model.rename(file, to: name) } } } .sheet(isPresented: $isCreatingFolder) { CreateFolderSheet(name: $folderName) { name in isCreatingFolder = false folderName = "" Task { await model.createFolder(name: name) } } onCancel: { isCreatingFolder = false; folderName = "" } } .sheet(isPresented: $isShowingSettings) { WorkspaceSettings(model: model) } .sheet(isPresented: $isShowingDuplicates) { DuplicateResults(model: model) } .sheet(item: $previewFile) { file in FilePreviewSheet(model: model, file: file) } .fileImporter(isPresented: $isImporting, allowedContentTypes: [.item], allowsMultipleSelection: false) { result in switch result { case .success(let urls): if let url = urls.first { Task { await model.upload(url: url) } } case .failure(let error): model.errorMessage = error.localizedDescription } } .alert("提示", isPresented: Binding(get: { !model.lastActionMessage.isEmpty }, set: { if !$0 { model.lastActionMessage = "" } })) { Button("确定", role: .cancel) { model.lastActionMessage = "" } } message: { Text(model.lastActionMessage) } .task { if model.files.isEmpty && !model.isLoadingFiles { await model.refresh() } } .onChange(of: selectedTool) { _, tool in if tool != nil { isInspectorPresented = false } } .onChange(of: model.section) { _, _ in selectedFileIDs = [] selectedTool = nil isInspectorPresented = false globalSearchText = "" Task { await model.refresh() } } .onChange(of: model.files.map(\.id)) { _, ids in selectedFileIDs.formIntersection(Set(ids)) if selectedFileIDs.isEmpty { isInspectorPresented = false } } .onChange(of: model.folderPath) { _, _ in selectedFileIDs = [] isInspectorPresented = false globalSearchText = "" } } } private struct AppTitleToolbar: View { @ObservedObject var model: AppModel let selectedTool: WorkspaceTool? @Binding var searchText: String private var supportsSearch: Bool { selectedTool == nil && [.files, .photos, .videos, .audio, .documents].contains(model.section) } var body: some View { HStack(spacing: 12) { Label(selectedTool?.rawValue ?? model.section.rawValue, systemImage: selectedTool?.icon ?? model.section.icon) .font(.headline.weight(.semibold)) if supportsSearch { TextField("搜索当前列表", text: $searchText) .textFieldStyle(.roundedBorder) .frame(width: 260) } } } }