import AppKit import SwiftUI import ShotdeckCore struct SettingsView: View { @Environment(AppModel.self) private var model private let labelWidth: CGFloat = 104 var body: some View { Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 10) { GridRow { Text("Hotkey") .font(.headline) .frame(maxWidth: .infinity, alignment: .leading) .gridCellColumns(2) } GridRow(alignment: .firstTextBaseline) { fieldLabel("Capture") Text("⌥⇧2 — fixed in this version") .foregroundStyle(.secondary) .lineLimit(1) .frame(maxWidth: .infinity, alignment: .leading) .frame(minHeight: 22, alignment: .leading) } GridRow { Text("Folders") .font(.headline) .frame(maxWidth: .infinity, alignment: .leading) .gridCellColumns(2) .padding(.top, 6) } GridRow(alignment: .center) { fieldLabel("Watch folder") folderValue(path: model.watchFolderURL.path) { model.chooseWatchFolder() } } GridRow(alignment: .center) { fieldLabel("Output folder") folderValue(path: model.outboxURL.path) { model.chooseOutboxFolder() } } GridRow { Button("Reveal spool folder") { model.openSpoolFolder() } .gridCellColumns(2) .frame(maxWidth: .infinity, alignment: .leading) .padding(.top, 4) } } .padding(16) .frame(minWidth: 320, idealWidth: 360, maxWidth: 360, alignment: .leading) .controlSize(.small) } private func fieldLabel(_ title: String) -> some View { Text(title) .lineLimit(1) .frame(width: labelWidth, alignment: .trailing) .gridColumnAlignment(.trailing) .frame(minHeight: 22, alignment: .trailing) } private func folderValue(path: String, choose: @escaping () -> Void) -> some View { HStack(spacing: 8) { Text(path) .lineLimit(1) .truncationMode(.middle) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) Button("Choose…") { choose() } } .frame(minHeight: 22) } } extension AppModel: SettingsWindowPresenting { private static var settingsWindowController: NSWindowController? public func presentSettingsWindow() { if let existing = Self.settingsWindowController { existing.window?.makeKeyAndOrderFront(nil) NSApp.activate() return } let hosting = NSHostingController(rootView: SettingsView().environment(self)) let window = NSWindow(contentViewController: hosting) window.title = "Redline Settings" window.styleMask = [.titled, .closable] window.isReleasedWhenClosed = false window.center() let controller = NSWindowController(window: window) Self.settingsWindowController = controller controller.showWindow(nil) NSApp.activate() } func chooseOutboxFolder() { guard let url = chooseDirectory(startingAt: outboxURL) else { return } FolderSettings.setOutbox(url) setFolderURLs(outbox: url, watch: watchFolderURL) setStatus("Output folder set to \(url.lastPathComponent).") } func chooseWatchFolder() { guard let url = chooseDirectory(startingAt: watchFolderURL) else { return } FolderSettings.setWatchFolder(url) setFolderURLs(outbox: outboxURL, watch: url) Task { do { try await watcher.updateWatchFolder(url) setStatus("Watch folder set to \(url.lastPathComponent).") } catch { setStatus( (error as? ShotdeckError)?.errorDescription ?? "Could not switch the watch folder." ) } } } private func chooseDirectory(startingAt directory: URL) -> URL? { let panel = NSOpenPanel() panel.canChooseDirectories = true panel.canChooseFiles = false panel.allowsMultipleSelection = false panel.canCreateDirectories = true panel.prompt = "Choose" panel.directoryURL = directory guard panel.runModal() == .OK else { return nil } return panel.url } }