import SwiftUI struct WorkspacesSettingsView: View { @ObservedObject private var workspaceRegistry = WorkspaceRegistry.shared @ObservedObject private var screenRegistry = ScreenRegistry.shared @State private var selectedWorkspaceID: WorkspaceID? @State private var renameDraft = "" @State private var isDeleteAlertPresented = false private var effectiveSelectedWorkspaceID: WorkspaceID? { selectedWorkspaceID ?? workspaceRegistry.workspaceSummaries.first?.id } private var selectedSummary: WorkspaceSummary? { guard let effectiveSelectedWorkspaceID else { return nil } return workspaceRegistry.summary(for: effectiveSelectedWorkspaceID) } private var selectedController: WorkspaceController? { guard let effectiveSelectedWorkspaceID else { return nil } return workspaceRegistry.controller(for: effectiveSelectedWorkspaceID) } private var selectedAssignedScreenIDs: [ScreenID] { guard let effectiveSelectedWorkspaceID else { return [] } return screenRegistry.assignedScreenIDs(to: effectiveSelectedWorkspaceID) } private var connectedScreenSummaries: [ConnectedScreenSummary] { screenRegistry.connectedScreenSummaries() } private var activeConnectedScreenSummary: ConnectedScreenSummary? { connectedScreenSummaries.first(where: \.isActive) } private var deletionFallbackSummary: WorkspaceSummary? { guard let effectiveSelectedWorkspaceID, let fallbackID = workspaceRegistry.deletionFallbackWorkspaceID( forDeleting: effectiveSelectedWorkspaceID, preferredFallback: workspaceRegistry.defaultWorkspaceID ) else { return nil } return workspaceRegistry.summary(for: fallbackID) } var body: some View { HStack(spacing: 20) { List(selection: $selectedWorkspaceID) { ForEach(workspaceRegistry.workspaceSummaries) { summary in VStack(alignment: .leading, spacing: 4) { Text(summary.name) .font(.headline) Text(usageDescription(for: summary)) .font(.caption) .foregroundStyle(.secondary) } .tag(summary.id) .accessibilityIdentifier("settings.workspace.row.\(summary.id.uuidString)") } } .accessibilityIdentifier("settings.workspaces.list") .frame(minWidth: 220, idealWidth: 240, maxWidth: 260, maxHeight: .infinity) if let summary = selectedSummary { Form { Section("Identity") { TextField("Workspace name", text: $renameDraft) .accessibilityIdentifier("settings.workspaces.name-field") .onSubmit { renameSelectedWorkspace() } OptionalHotkeyRecorderView( label: "Jump Hotkey", binding: workspaceHotkeyBinding(for: summary.id) ) HStack { Button("Save Name") { renameSelectedWorkspace() } .accessibilityIdentifier("settings.workspaces.save-name") Button("New Workspace") { createWorkspace() } .accessibilityIdentifier("settings.workspaces.new") } Text("Workspace jump hotkeys are active when the notch is open and switch the current screen to this workspace.") .font(.caption) .foregroundStyle(.secondary) } Section("Usage") { LabeledContent("Assigned screens") { Text("\(selectedAssignedScreenIDs.count)") .accessibilityIdentifier("settings.workspaces.assigned-count") } LabeledContent("Open tabs") { Text("\(selectedController?.tabs.count ?? 0)") } if selectedAssignedScreenIDs.isEmpty { Text("No screens are currently assigned to this workspace.") .foregroundStyle(.secondary) } else { ForEach(selectedAssignedScreenIDs, id: \.self) { screenID in LabeledContent("Screen") { Text(screenID) .font(.caption.monospaced()) } } } } Section("Shared Workspace Rules") { Text(sharedWorkspaceDescription(for: selectedAssignedScreenIDs.count)) .foregroundStyle(.secondary) } Section("Connected Screens") { if let activeScreen = activeConnectedScreenSummary { HStack { VStack(alignment: .leading, spacing: 4) { Text(activeScreen.displayName) Text(activeScreen.id) .font(.caption.monospaced()) .foregroundStyle(.secondary) } Spacer() Button(activeScreen.assignedWorkspaceID == summary.id ? "Assigned Here" : "Assign Current Screen") { screenRegistry.assignWorkspace(summary.id, to: activeScreen.id) } .accessibilityIdentifier("settings.workspaces.assign-current") .disabled(activeScreen.assignedWorkspaceID == summary.id) } } else { Text("No connected screens are currently available.") .foregroundStyle(.secondary) } ForEach(connectedScreenSummaries.filter { !$0.isActive }) { screen in HStack { VStack(alignment: .leading, spacing: 4) { Text(screen.displayName) Text(screen.id) .font(.caption.monospaced()) .foregroundStyle(.secondary) } Spacer() Button(screen.assignedWorkspaceID == summary.id ? "Assigned Here" : "Assign Here") { screenRegistry.assignWorkspace(summary.id, to: screen.id) } .accessibilityIdentifier("settings.workspaces.assign.\(screen.id)") .disabled(screen.assignedWorkspaceID == summary.id) } } } Section("Danger Zone") { Button("Delete Workspace", role: .destructive) { isDeleteAlertPresented = true } .accessibilityIdentifier("settings.workspaces.delete") .disabled(!workspaceRegistry.canDeleteWorkspace(id: summary.id)) if !workspaceRegistry.canDeleteWorkspace(id: summary.id) { Text("At least one workspace must remain.") .foregroundStyle(.secondary) } } } .formStyle(.grouped) } else { ContentUnavailableView( "No Workspaces", systemImage: "rectangle.3.group", description: Text("Create a workspace to start grouping tabs across screens.") ) } } .onAppear { selectInitialWorkspaceIfNeeded() } .onChange(of: workspaceRegistry.workspaceSummaries) { _, _ in synchronizeSelectionWithRegistry() } .onChange(of: selectedWorkspaceID) { _, _ in renameDraft = selectedSummary?.name ?? "" } .alert("Delete Workspace", isPresented: $isDeleteAlertPresented) { Button("Cancel", role: .cancel) {} Button("Delete", role: .destructive) { deleteSelectedWorkspace() } } message: { if let summary = selectedSummary, let fallback = deletionFallbackSummary { Text( "Deleting \(summary.name) reassigns its screens to \(fallback.name) and closes the workspace." ) } else { Text("At least one workspace must remain.") } } } private func usageDescription(for summary: WorkspaceSummary) -> String { let screenCount = screenRegistry.assignedScreenCount(to: summary.id) let tabCount = workspaceRegistry.controller(for: summary.id)?.tabs.count ?? 0 return "\(screenCount) screen\(screenCount == 1 ? "" : "s") ยท \(tabCount) tab\(tabCount == 1 ? "" : "s")" } private func sharedWorkspaceDescription(for screenCount: Int) -> String { if screenCount > 1 { return "This workspace is shared across \(screenCount) screens. Tab changes stay in sync across each assigned screen." } if screenCount == 1 { return "This workspace is assigned to one screen. You can assign additional screens to share the same tabs." } return "Unassigned workspaces keep their tabs and can be attached to any screen later." } private func selectInitialWorkspaceIfNeeded() { if selectedWorkspaceID == nil { selectedWorkspaceID = workspaceRegistry.workspaceSummaries.first?.id } renameDraft = selectedSummary?.name ?? "" } private func synchronizeSelectionWithRegistry() { guard let selectedWorkspaceID else { selectInitialWorkspaceIfNeeded() return } if workspaceRegistry.summary(for: selectedWorkspaceID) == nil { self.selectedWorkspaceID = workspaceRegistry.workspaceSummaries.first?.id } renameDraft = selectedSummary?.name ?? "" } private func renameSelectedWorkspace() { guard let effectiveSelectedWorkspaceID else { return } workspaceRegistry.renameWorkspace(id: effectiveSelectedWorkspaceID, to: renameDraft) renameDraft = selectedSummary?.name ?? renameDraft } private func createWorkspace() { let workspaceID = workspaceRegistry.createWorkspace() selectedWorkspaceID = workspaceID renameDraft = workspaceRegistry.summary(for: workspaceID)?.name ?? "" } private func workspaceHotkeyBinding(for workspaceID: WorkspaceID) -> Binding { Binding( get: { workspaceRegistry.summary(for: workspaceID)?.hotkey }, set: { newValue in workspaceRegistry.updateWorkspaceHotkey(id: workspaceID, to: newValue) } ) } private func deleteSelectedWorkspace() { guard let effectiveSelectedWorkspaceID, let fallbackWorkspaceID = screenRegistry.deleteWorkspace( effectiveSelectedWorkspaceID, preferredFallback: workspaceRegistry.defaultWorkspaceID ) else { return } self.selectedWorkspaceID = fallbackWorkspaceID renameDraft = workspaceRegistry.summary(for: fallbackWorkspaceID)?.name ?? "" } }