From fb3ece20c8bc462dc96bbf6cff27948f43300405 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 2 Jun 2026 01:12:58 +0700 Subject: [PATCH] feat(toolbar): make connection selectors navigational and move refresh/save to the trailing side --- CHANGELOG.md | 1 + .../Infrastructure/MainWindowToolbar.swift | 8 +- TablePro/Resources/Localizable.xcstrings | 218 ++++++++++++++++-- .../MainWindowToolbarValidationTests.swift | 2 +- 4 files changed, 212 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee8eef718..86f350deb 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 - Connection list rows show the database name after the host, so connections that share a name and host are easier to tell apart. (#1535) - Save as Favorite uses Cmd+D again. The Cmd+Control+D set in 0.47.0 is reserved by macOS for Look Up, so it never fired. - Editor toolbar buttons show their keyboard shortcut in the tooltip, and it updates if you rebind the shortcut. +- Window toolbar layout: the connection and database selectors are now navigation items pinned to the left, and the Refresh and Save buttons move to the right next to the tab and query controls. Toolbars customized before this change reset once to the new layout. ### Fixed diff --git a/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift b/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift index 63618d593..401954d4f 100644 --- a/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift +++ b/TablePro/Core/Services/Infrastructure/MainWindowToolbar.swift @@ -13,7 +13,7 @@ import TableProPluginKit internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { private static let lifecycleLogger = Logger(subsystem: "com.TablePro", category: "NativeTabLifecycle") - internal static let toolbarIdentifier = NSToolbar.Identifier("com.TablePro.main.toolbar") + internal static let toolbarIdentifier = NSToolbar.Identifier("com.TablePro.main.toolbar.v2") weak var coordinator: MainContentCoordinator? @@ -77,9 +77,9 @@ internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { Self.sidebarToggle, .sidebarTrackingSeparator, Self.connectionGroup, - Self.refreshSaveGroup, Self.principal, .flexibleSpace, + Self.refreshSaveGroup, Self.quickSwitcher, Self.newTab, Self.previewSQL, @@ -110,7 +110,7 @@ internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { case Self.sidebarToggle: return makeSidebarToggleItem(coordinator: coordinator) case Self.connectionGroup: - return makeGroup( + let group = makeGroup( id: itemIdentifier, label: String(localized: "Connection"), subitems: [subitemConnection(), subitemDatabase()], @@ -119,6 +119,8 @@ internal final class MainWindowToolbar: NSObject, NSToolbarDelegate { DatabaseToolbarButton(coordinator: coordinator) } ) + group.isNavigational = true + return group case Self.principal: let item = hostingItem( id: itemIdentifier, diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 5a366ddb3..75c44ef5d 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -1927,6 +1927,7 @@ } }, "%lld of %lld" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -6415,6 +6416,9 @@ } } } + }, + "Apply Schema Changes" : { + }, "Apply this filter" : { "extractionState" : "stale", @@ -7031,6 +7035,9 @@ } } } + }, + "Authentication is required for this operation" : { + }, "Authentication Method" : { @@ -7058,6 +7065,7 @@ } }, "Authentication required to execute operations" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -8358,12 +8366,8 @@ } } } - }, - "Cannot execute write queries: connection is read only" : { - }, "Cannot execute write queries: connection is read-only" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -8412,9 +8416,6 @@ }, "Cannot read ~/.aws/credentials." : { - }, - "Cannot save changes: connection is read only" : { - }, "Cannot save changes: connection is read-only" : { "extractionState" : "stale", @@ -8705,6 +8706,7 @@ }, "Cell Value" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -9012,6 +9014,9 @@ "Choose" : { "comment" : "Button text for choosing a backup file.", "isCommentAutoGenerated" : true + }, + "Choose a %@ export file to import" : { + }, "Choose a certificate or key file" : { "localizations" : { @@ -9157,6 +9162,9 @@ }, "Choose AI provider and model" : { + }, + "Choose an export file to import" : { + }, "Choose Destination…" : { @@ -10401,7 +10409,6 @@ } }, "Column" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -10547,6 +10554,9 @@ } } } + }, + "Column names must be unique." : { + }, "Column Reorder Failed" : { "localizations" : { @@ -11000,6 +11010,9 @@ } } } + }, + "Confirmation is required for this operation" : { + }, "Connect" : { "localizations" : { @@ -12832,6 +12845,9 @@ } } } + }, + "Could not build the CREATE TABLE statement" : { + }, "Could not create destination file" : { "localizations" : { @@ -13023,6 +13039,9 @@ } } } + }, + "Could not read password file: %@" : { + }, "Could not read the file. It may have been deleted or moved." : { @@ -13203,6 +13222,12 @@ } } } + }, + "Create a new table or view" : { + + }, + "Create all columns" : { + }, "Create connection..." : { "extractionState" : "stale", @@ -13348,10 +13373,6 @@ } } }, - "Create New Table" : { - "comment" : "Tooltip and accessibility label for the button that allows the user to create a new table.", - "isCommentAutoGenerated" : true - }, "Create New Table..." : { "extractionState" : "stale", "localizations" : { @@ -13724,6 +13745,9 @@ }, "Current Query" : { + }, + "Current schema" : { + }, "Current schema: %@ (⌘K to switch)" : { "extractionState" : "stale", @@ -14716,6 +14740,9 @@ } } } + }, + "db %d" : { + }, "Deactivate" : { "localizations" : { @@ -15985,6 +16012,9 @@ } } } + }, + "Destination:" : { + }, "Destructive Changes" : { "localizations" : { @@ -16007,6 +16037,9 @@ } } } + }, + "Destructive operations are not permitted for this client" : { + }, "Detach" : { "localizations" : { @@ -17272,6 +17305,9 @@ } } } + }, + "Each column can be mapped from only one field." : { + }, "Each SQLite file is a separate database.\nTo open a different database, create a new connection." : { "extractionState" : "stale", @@ -18407,6 +18443,9 @@ }, "Enterprise SQL with PL/SQL" : { + }, + "Environment variable %@ is not set in TablePro's environment. Apps launched from the Dock do not inherit shell exports. Launch TablePro from a terminal, or set the variable with launchctl setenv." : { + }, "Environment Variables" : { "localizations" : { @@ -18627,6 +18666,9 @@ } } } + }, + "Every included column needs a name." : { + }, "Every table needs at least one column. Click + to get started" : { "localizations" : { @@ -18754,6 +18796,7 @@ }, "Execute All" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -18951,6 +18994,9 @@ } } } + }, + "Existing table" : { + }, "expand" : { "localizations" : { @@ -22245,6 +22291,9 @@ }, "Focus Query Tab" : { + }, + "Focus Sidebar Filter" : { + }, "Focus the query editor to insert" : { "extractionState" : "stale", @@ -22455,6 +22504,7 @@ } }, "Format JSON" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -22499,6 +22549,7 @@ } }, "Format Query (⇧⌘L)" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -22630,6 +22681,9 @@ } } } + }, + "From %@…" : { + }, "FULL (rewrites entire table, blocks access)" : { "localizations" : { @@ -23934,6 +23988,12 @@ } } } + }, + "Import %@…" : { + + }, + "Import all fields" : { + }, "Import cancelled by user" : { "extractionState" : "stale", @@ -24345,6 +24405,15 @@ }, "Import from URL..." : { + }, + "Import into:" : { + + }, + "Import is not supported for %@ connections." : { + + }, + "Import JSON rows into a table" : { + }, "Import Not Supported" : { "localizations" : { @@ -24434,6 +24503,9 @@ } } } + }, + "Import…" : { + }, "Importing passwords from %1$@ reads up to %2$d keychain items. macOS prompts for your login password once per item because each is owned by %1$@. Click Always Allow on each prompt to grant TablePro permanent access. Cancel any prompt to skip the rest." : { @@ -26134,6 +26206,9 @@ } } } + }, + "JSON field" : { + }, "JSON Too Large" : { "localizations" : { @@ -26348,6 +26423,9 @@ } } } + }, + "Key" : { + }, "Key File" : { "localizations" : { @@ -26370,6 +26448,9 @@ } } } + }, + "Key Passphrase" : { + }, "Key Prefix Root" : { "localizations" : { @@ -27298,6 +27379,7 @@ } }, "Limit" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -29437,6 +29519,9 @@ } } } + }, + "Multiple statements are not permitted for this client" : { + }, "Multiple values" : { "localizations" : { @@ -29512,6 +29597,9 @@ } } } + }, + "name" : { + }, "Name" : { "localizations" : { @@ -30150,6 +30238,15 @@ } } } + }, + "New table" : { + + }, + "New Table" : { + + }, + "New table:" : { + }, "New Theme" : { "localizations" : { @@ -30172,6 +30269,9 @@ } } } + }, + "New View" : { + }, "New View..." : { "localizations" : { @@ -30221,6 +30321,7 @@ } }, "Next Page (⌘])" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32022,6 +32123,7 @@ } }, "Not connected" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -32352,6 +32454,9 @@ } } } + }, + "Null" : { + }, "NULL" : { "localizations" : { @@ -32665,6 +32770,7 @@ } }, "Offset" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -33402,6 +33508,9 @@ }, "Operation Failed" : { + }, + "Operation not permitted" : { + }, "Operator" : { "localizations" : { @@ -33915,6 +34024,7 @@ } }, "Pagination Settings" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -34200,6 +34310,28 @@ } } } + }, + "Password command failed (exit %d): %@" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Password command failed (exit %1$d): %2$@" + } + } + } + }, + "Password command failed with exit code %d" : { + + }, + "Password command produced too much output" : { + + }, + "Password command timed out after 30 seconds" : { + + }, + "Password file not found: %@" : { + }, "Password is sent via keyboard-interactive challenge-response." : { "localizations" : { @@ -35206,6 +35338,7 @@ } }, "Potentially Dangerous Queries" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -35228,6 +35361,7 @@ } }, "Potentially Dangerous Query" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -35945,6 +36079,7 @@ } }, "Previous Page (⌘[)" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -37545,7 +37680,6 @@ } }, "Read-Only" : { - "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -38795,6 +38929,9 @@ } } } + }, + "Reorder Column" : { + }, "Replace" : { "localizations" : { @@ -38973,6 +39110,9 @@ } } } + }, + "Required only for an encrypted key" : { + }, "Required only when the server enforces mutual TLS authentication." : { @@ -40834,6 +40974,9 @@ } } } + }, + "Saved passwords are decrypted during import" : { + }, "Saved Query" : { @@ -40905,6 +41048,9 @@ } } } + }, + "Schema change was not authorized" : { + }, "Schema for %@" : { @@ -40942,6 +41088,9 @@ }, "Schema Switching Not Supported" : { + }, + "Schema: %@" : { + }, "Schemas" : { "localizations" : { @@ -41568,6 +41717,9 @@ } } } + }, + "Select %@ file to import" : { + }, "Select a dump file produced by pg_dump in custom archive format." : { @@ -41682,6 +41834,9 @@ } } } + }, + "Select a table…" : { + }, "Select All" : { "localizations" : { @@ -42890,6 +43045,12 @@ }, "Show details" : { + }, + "Show ER Diagram" : { + + }, + "Show Favorites Sidebar" : { + }, "Show in Finder" : { @@ -43006,6 +43167,9 @@ }, "Show System Schemas" : { + }, + "Show Tables Sidebar" : { + }, "Show Welcome Screen" : { "localizations" : { @@ -43507,6 +43671,9 @@ } } } + }, + "Skips foreign key constraint checks for this operation" : { + }, "Slash commands" : { @@ -44121,6 +44288,7 @@ } }, "SQL import is not supported for %@ connections." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -44238,6 +44406,9 @@ } } } + }, + "SQL query editor" : { + }, "SQL query to export results from" : { @@ -46643,6 +46814,9 @@ } } } + }, + "Table Favorites:" : { + }, "Table Info" : { "extractionState" : "stale", @@ -46720,6 +46894,9 @@ }, "Table names to export (alternative to query)" : { + }, + "table_name" : { + }, "Table: %@" : { "extractionState" : "stale", @@ -47498,6 +47675,7 @@ } }, "The following %d queries may permanently modify or delete data. This action cannot be undone.\n\n%@" : { + "extractionState" : "stale", "localizations" : { "en" : { "stringUnit" : { @@ -47691,6 +47869,9 @@ "The partial backup file will be removed." : { "comment" : "A message displayed in a cancel confirmation alert for a backup.", "isCommentAutoGenerated" : true + }, + "The password source produced an empty password" : { + }, "The previous code wasn't accepted. Wait for your authenticator to refresh, then enter the new code." : { @@ -47948,6 +48129,7 @@ } }, "This DELETE query has no WHERE clause and will delete ALL rows in the table. This action cannot be undone." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -47979,6 +48161,7 @@ }, "This DROP query will permanently remove database objects. This action cannot be undone." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -48452,8 +48635,12 @@ }, "This project has no datasets yet." : { + }, + "This query may permanently modify or delete data and cannot be undone.\n\n%@" : { + }, "This query may permanently modify or delete data." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -48558,6 +48745,7 @@ } }, "This TRUNCATE query will permanently delete all rows in the table. This action cannot be undone." : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -49206,6 +49394,7 @@ } }, "Toggle Filters (⇧⌘F)" : { + "extractionState" : "stale", "localizations" : { "tr" : { "stringUnit" : { @@ -52617,6 +52806,9 @@ } } } + }, + "Write operations are not permitted for this client" : { + }, "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" : { "localizations" : { diff --git a/TableProTests/Services/MainWindowToolbarValidationTests.swift b/TableProTests/Services/MainWindowToolbarValidationTests.swift index ca8996ecb..dbeb914b1 100644 --- a/TableProTests/Services/MainWindowToolbarValidationTests.swift +++ b/TableProTests/Services/MainWindowToolbarValidationTests.swift @@ -145,7 +145,7 @@ struct MainWindowToolbarValidationTests { @Test("Toolbar identifier is stable across instances so AppKit autosave can persist customizations") func toolbarIdentifierIsStable() { - #expect(MainWindowToolbar.toolbarIdentifier == "com.TablePro.main.toolbar") + #expect(MainWindowToolbar.toolbarIdentifier == "com.TablePro.main.toolbar.v2") } @Test("Toolbar is configured for user customization and autosave")