diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b03738..d1b223f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add schema versioning to SQLite databases (query history, favorites) for future migrations - Use semantic selected-text color instead of hardcoded white in selected rows - Use proper CommandGroup for full-screen shortcut instead of event monitor +- Use sheet presentation for all file open/save panels instead of free-floating dialogs ### Added diff --git a/TablePro/ViewModels/WelcomeViewModel.swift b/TablePro/ViewModels/WelcomeViewModel.swift index 0ebd67a7..198952d5 100644 --- a/TablePro/ViewModels/WelcomeViewModel.swift +++ b/TablePro/ViewModels/WelcomeViewModel.swift @@ -377,8 +377,11 @@ final class WelcomeViewModel { panel.allowedContentTypes = [.tableproConnectionShare] panel.allowsMultipleSelection = false panel.canChooseDirectories = false - guard panel.runModal() == .OK, let url = panel.url else { return } - activeSheet = .importFile(url) + guard let window = NSApp.keyWindow else { return } + panel.beginSheetModal(for: window) { response in + guard response == .OK, let url = panel.url else { return } + self.activeSheet = .importFile(url) + } } func showImportResultAlert(count: Int) { diff --git a/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift b/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift index fb9b0042..8e8f540b 100644 --- a/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift +++ b/TablePro/Views/Connection/ConnectionExportOptionsSheet.swift @@ -104,24 +104,27 @@ struct ConnectionExportOptionsSheet: View { : "Connections.tablepro" panel.nameFieldStringValue = defaultName panel.canCreateDirectories = true - guard panel.runModal() == .OK, let url = panel.url else { return } - - do { - if shouldEncrypt { - try ConnectionExportService.exportConnectionsEncrypted( - capturedConnections, - to: url, - passphrase: capturedPassphrase + guard let window = NSApp.keyWindow else { return } + panel.beginSheetModal(for: window) { response in + guard response == .OK, let url = panel.url else { return } + + do { + if shouldEncrypt { + try ConnectionExportService.exportConnectionsEncrypted( + capturedConnections, + to: url, + passphrase: capturedPassphrase + ) + } else { + try ConnectionExportService.exportConnections(capturedConnections, to: url) + } + } catch { + AlertHelper.showErrorSheet( + title: String(localized: "Export Failed"), + message: error.localizedDescription, + window: window ) - } else { - try ConnectionExportService.exportConnections(capturedConnections, to: url) } - } catch { - AlertHelper.showErrorSheet( - title: String(localized: "Export Failed"), - message: error.localizedDescription, - window: NSApp.keyWindow - ) } } } diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index 8f424a1c..4db09e90 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -714,7 +714,9 @@ struct ExportDialog: View { savePanel.message = String(format: String(localized: "Export %d table(s) to %@"), exportableCount, formatName) } - savePanel.begin { response in + guard let keyWindow = NSApp.keyWindow else { return } + let window = keyWindow.sheetParent ?? keyWindow + savePanel.beginSheetModal(for: window) { response in guard response == .OK, let url = savePanel.url else { return } Task { diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index 0b2b96b8..dcbf4d3a 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -294,11 +294,13 @@ struct ImportDialog: View { panel.allowsMultipleSelection = false panel.message = "Select file to import" - panel.begin { response in + guard let keyWindow = NSApp.keyWindow else { return } + let window = keyWindow.sheetParent ?? keyWindow + panel.beginSheetModal(for: window) { response in guard response == .OK, let url = panel.url else { return } - loadFileTask = Task { - await loadFile(url) + self.loadFileTask = Task { + await self.loadFile(url) } } } diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift index 0ed5fdcb..cbea44ec 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+SidebarActions.swift @@ -133,7 +133,8 @@ extension MainContentCoordinator { panel.allowsMultipleSelection = false panel.message = "Select SQL file to import" - panel.begin { [weak self] response in + guard let window = contentWindow else { return } + panel.beginSheetModal(for: window) { [weak self] response in guard response == .OK, let url = panel.url else { return } self?.importFileURL = url self?.activeSheet = .importDialog diff --git a/TablePro/Views/Settings/Appearance/ThemeListView.swift b/TablePro/Views/Settings/Appearance/ThemeListView.swift index 23f7fce5..a85e4c22 100644 --- a/TablePro/Views/Settings/Appearance/ThemeListView.swift +++ b/TablePro/Views/Settings/Appearance/ThemeListView.swift @@ -173,26 +173,32 @@ internal struct ThemeListView: View { } private func exportActiveTheme() { + guard let window = NSApp.keyWindow else { return } let panel = NSSavePanel() panel.allowedContentTypes = [.json] panel.nameFieldStringValue = engine.activeTheme.name + ".json" panel.canCreateDirectories = true - guard panel.runModal() == .OK, let url = panel.url else { return } - try? engine.exportTheme(engine.activeTheme, to: url) + panel.beginSheetModal(for: window) { response in + guard response == .OK, let url = panel.url else { return } + try? engine.exportTheme(engine.activeTheme, to: url) + } } private func importTheme() { + guard let window = NSApp.keyWindow else { return } let panel = NSOpenPanel() panel.allowedContentTypes = [.json] panel.allowsMultipleSelection = false panel.canChooseDirectories = false - guard panel.runModal() == .OK, let url = panel.url else { return } - do { - let imported = try engine.importTheme(from: url) - selectedThemeId = imported.id - } catch { - errorMessage = error.localizedDescription - showError = true + panel.beginSheetModal(for: window) { response in + guard response == .OK, let url = panel.url else { return } + do { + let imported = try self.engine.importTheme(from: url) + self.selectedThemeId = imported.id + } catch { + self.errorMessage = error.localizedDescription + self.showError = true + } } } } diff --git a/TablePro/Views/Settings/LinkedFoldersSection.swift b/TablePro/Views/Settings/LinkedFoldersSection.swift index 18a010b8..535fb6ba 100644 --- a/TablePro/Views/Settings/LinkedFoldersSection.swift +++ b/TablePro/Views/Settings/LinkedFoldersSection.swift @@ -101,15 +101,16 @@ struct LinkedFoldersSection: View { panel.allowsMultipleSelection = false panel.message = String(localized: "Choose a folder to watch for .tablepro connection files") - panel.begin { response in + guard let window = NSApp.keyWindow else { return } + panel.beginSheetModal(for: window) { response in guard response == .OK, let url = panel.url else { return } let path = PathPortability.contractHome(url.path) - guard !folders.contains(where: { $0.path == path }) else { return } + guard !self.folders.contains(where: { $0.path == path }) else { return } let folder = LinkedFolder(path: path) LinkedFolderStorage.shared.addFolder(folder) - folders = LinkedFolderStorage.shared.loadFolders() + self.folders = LinkedFolderStorage.shared.loadFolders() LinkedFolderWatcher.shared.reload() } } diff --git a/TablePro/Views/Settings/Plugins/InstalledPluginsView.swift b/TablePro/Views/Settings/Plugins/InstalledPluginsView.swift index 88aad66f..37365f14 100644 --- a/TablePro/Views/Settings/Plugins/InstalledPluginsView.swift +++ b/TablePro/Views/Settings/Plugins/InstalledPluginsView.swift @@ -297,9 +297,11 @@ struct InstalledPluginsView: View { panel.canChooseDirectories = false panel.treatsFilePackagesAsDirectories = false - guard panel.runModal() == .OK, let url = panel.url else { return } - - installPlugin(from: url) + guard let window = NSApp.keyWindow else { return } + panel.beginSheetModal(for: window) { response in + guard response == .OK, let url = panel.url else { return } + self.installPlugin(from: url) + } } private func installPlugin(from url: URL) { diff --git a/TablePro/Views/Structure/TableStructureView.swift b/TablePro/Views/Structure/TableStructureView.swift index 31f9dc1d..e7c1592c 100644 --- a/TablePro/Views/Structure/TableStructureView.swift +++ b/TablePro/Views/Structure/TableStructureView.swift @@ -979,7 +979,8 @@ struct TableStructureView: View { } savePanel.nameFieldStringValue = "\(tableName).sql" - savePanel.begin { response in + guard let window = NSApp.keyWindow else { return } + savePanel.beginSheetModal(for: window) { response in guard response == .OK, let url = savePanel.url else { return } do { try ddlStatement.write(to: url, atomically: true, encoding: .utf8)