From be974c9522694213204abeef959bc0e6bcd5fa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Mon, 6 Apr 2026 13:31:54 +0700 Subject: [PATCH 1/2] fix: convert all file open/save panels to sheet presentation --- CHANGELOG.md | 1 + TablePro/ViewModels/WelcomeViewModel.swift | 7 ++-- .../ConnectionExportOptionsSheet.swift | 35 ++++++++++--------- TablePro/Views/Export/ExportDialog.swift | 3 +- TablePro/Views/Import/ImportDialog.swift | 7 ++-- ...ainContentCoordinator+SidebarActions.swift | 3 +- .../Settings/Appearance/ThemeListView.swift | 24 ++++++++----- .../Views/Settings/LinkedFoldersSection.swift | 7 ++-- .../Plugins/InstalledPluginsView.swift | 8 +++-- .../Views/Structure/TableStructureView.swift | 3 +- 10 files changed, 59 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b03738f..d1b223f8c 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 0ebd67a74..198952d55 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 fb9b00427..8e8f540b0 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 8f424a1c5..dfa869bf7 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -714,7 +714,8 @@ struct ExportDialog: View { savePanel.message = String(format: String(localized: "Export %d table(s) to %@"), exportableCount, formatName) } - 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 } Task { diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index 0b2b96b8a..65ead9a20 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -294,11 +294,12 @@ struct ImportDialog: View { panel.allowsMultipleSelection = false panel.message = "Select file to import" - 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 } - 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 0ed5fdcbd..cbea44ec9 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 23f7fce50..a85e4c222 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 18a010b8d..535fb6baf 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 88aad66fc..37365f14c 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 31f9dc1dd..e7c1592cf 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) From 0a4fc87ebf61cd1e2c740cf0f4447c0a8f7de963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ng=C3=B4=20Qu=E1=BB=91c=20=C4=90=E1=BA=A1t?= Date: Mon, 6 Apr 2026 13:40:42 +0700 Subject: [PATCH 2/2] fix: use sheetParent for file panels opened from sheet-presented dialogs --- TablePro/Views/Export/ExportDialog.swift | 3 ++- TablePro/Views/Import/ImportDialog.swift | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index dfa869bf7..4db09e90b 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -714,7 +714,8 @@ struct ExportDialog: View { savePanel.message = String(format: String(localized: "Export %d table(s) to %@"), exportableCount, formatName) } - guard let window = NSApp.keyWindow else { return } + 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 } diff --git a/TablePro/Views/Import/ImportDialog.swift b/TablePro/Views/Import/ImportDialog.swift index 65ead9a20..dcbf4d3a0 100644 --- a/TablePro/Views/Import/ImportDialog.swift +++ b/TablePro/Views/Import/ImportDialog.swift @@ -294,7 +294,8 @@ struct ImportDialog: View { panel.allowsMultipleSelection = false panel.message = "Select file to import" - guard let window = NSApp.keyWindow else { return } + 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 }