From 87a1b7377a253926edf04af58ccd809fbee0c799 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 17 Mar 2026 09:01:52 +0700 Subject: [PATCH 1/3] fix: use asset catalog icons for all plugins, remove etcd from built-in bundle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace SF Symbol iconName values with asset catalog names across all driver plugins (mysql-icon, postgresql-icon, sqlite-icon, etc.) - Remove EtcdDriverPlugin from Copy Plug-Ins build phase and main target dependencies — it is a registry plugin, not built-in - Mark etcd as isDownloadable and fix SQLite isDownloadable to false - Add TableProPluginKitVersion to Redis and Cassandra Info.plists - Preserve registry default icons in buildMetadataSnapshot so plugin registration does not overwrite curated SVG icons - Add DatabaseType.iconImage helper that handles both SF Symbols and asset catalog names, update all connection icon callsites - Add return after validateDriverDescriptor failure to prevent rejected plugins from proceeding to metadata registration --- Plugins/CassandraDriverPlugin/Info.plist | 2 ++ Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift | 2 +- Plugins/EtcdDriverPlugin/EtcdPlugin.swift | 3 ++- Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift | 2 +- Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift | 2 +- Plugins/MySQLDriverPlugin/MySQLPlugin.swift | 2 +- Plugins/OracleDriverPlugin/OraclePlugin.swift | 2 +- Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift | 2 +- Plugins/RedisDriverPlugin/Info.plist | 2 ++ Plugins/RedisDriverPlugin/RedisPlugin.swift | 2 +- Plugins/SQLiteDriverPlugin/SQLitePlugin.swift | 4 ++-- TablePro.xcodeproj/project.pbxproj | 4 +--- TablePro/AppDelegate+WindowConfig.swift | 5 ++++- TablePro/Core/Plugins/PluginManager.swift | 1 + TablePro/Core/Plugins/PluginMetadataRegistry.swift | 8 +++++++- TablePro/Models/Connection/DatabaseConnection.swift | 10 ++++++++++ TablePro/Views/Connection/ConnectionFormView.swift | 2 +- .../Views/Connection/ConnectionSidebarHeader.swift | 6 +++--- TablePro/Views/Connection/WelcomeWindowView.swift | 2 +- 19 files changed, 43 insertions(+), 20 deletions(-) diff --git a/Plugins/CassandraDriverPlugin/Info.plist b/Plugins/CassandraDriverPlugin/Info.plist index 737aa31f..c587cfa8 100644 --- a/Plugins/CassandraDriverPlugin/Info.plist +++ b/Plugins/CassandraDriverPlugin/Info.plist @@ -18,6 +18,8 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + TableProPluginKitVersion + 1 NSPrincipalClass $(PRODUCT_MODULE_NAME).CassandraPlugin diff --git a/Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift b/Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift index e3df957d..bf8d2dac 100644 --- a/Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift +++ b/Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift @@ -15,7 +15,7 @@ final class ClickHousePlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "ClickHouse" static let databaseDisplayName = "ClickHouse" - static let iconName = "bolt.fill" + static let iconName = "clickhouse-icon" static let defaultPort = 8123 // MARK: - UI/Capability Metadata diff --git a/Plugins/EtcdDriverPlugin/EtcdPlugin.swift b/Plugins/EtcdDriverPlugin/EtcdPlugin.swift index 98d9f7cb..a9c39b45 100644 --- a/Plugins/EtcdDriverPlugin/EtcdPlugin.swift +++ b/Plugins/EtcdDriverPlugin/EtcdPlugin.swift @@ -17,9 +17,10 @@ final class EtcdPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "etcd" static let databaseDisplayName = "etcd" - static let iconName = "cylinder.fill" + static let iconName = "etcd-icon" static let defaultPort = 2379 static let additionalDatabaseTypeIds: [String] = [] + static let isDownloadable = true static let navigationModel: NavigationModel = .standard static let pathFieldRole: PathFieldRole = .database diff --git a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift index a45f97d0..460141b0 100644 --- a/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift +++ b/Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift @@ -16,7 +16,7 @@ final class MSSQLPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "SQL Server" static let databaseDisplayName = "SQL Server" - static let iconName = "server.rack" + static let iconName = "mssql-icon" static let defaultPort = 1433 static let additionalConnectionFields: [ConnectionField] = [ ConnectionField(id: "mssqlSchema", label: "Schema", placeholder: "dbo", defaultValue: "dbo") diff --git a/Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift b/Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift index 668fcb19..a7b01ef3 100644 --- a/Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift +++ b/Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift @@ -14,7 +14,7 @@ final class MongoDBPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "MongoDB" static let databaseDisplayName = "MongoDB" - static let iconName = "leaf.fill" + static let iconName = "mongodb-icon" static let defaultPort = 27017 static let additionalConnectionFields: [ConnectionField] = [ ConnectionField(id: "mongoAuthSource", label: "Auth Database", placeholder: "admin"), diff --git a/Plugins/MySQLDriverPlugin/MySQLPlugin.swift b/Plugins/MySQLDriverPlugin/MySQLPlugin.swift index b5f1cefc..c0cbf9cf 100644 --- a/Plugins/MySQLDriverPlugin/MySQLPlugin.swift +++ b/Plugins/MySQLDriverPlugin/MySQLPlugin.swift @@ -20,7 +20,7 @@ final class MySQLPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "MySQL" static let databaseDisplayName = "MySQL" - static let iconName = "cylinder.fill" + static let iconName = "mysql-icon" static let defaultPort = 3306 static let additionalConnectionFields: [ConnectionField] = [] static let additionalDatabaseTypeIds: [String] = ["MariaDB"] diff --git a/Plugins/OracleDriverPlugin/OraclePlugin.swift b/Plugins/OracleDriverPlugin/OraclePlugin.swift index 0e9afa99..331d347c 100644 --- a/Plugins/OracleDriverPlugin/OraclePlugin.swift +++ b/Plugins/OracleDriverPlugin/OraclePlugin.swift @@ -15,7 +15,7 @@ final class OraclePlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "Oracle" static let databaseDisplayName = "Oracle" - static let iconName = "server.rack" + static let iconName = "oracle-icon" static let defaultPort = 1521 static let additionalConnectionFields: [ConnectionField] = [ ConnectionField(id: "oracleServiceName", label: "Service Name", placeholder: "ORCL") diff --git a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift index d77935ff..6b8b646e 100644 --- a/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift +++ b/Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift @@ -19,7 +19,7 @@ final class PostgreSQLPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "PostgreSQL" static let databaseDisplayName = "PostgreSQL" - static let iconName = "cylinder.fill" + static let iconName = "postgresql-icon" static let defaultPort = 5432 static let additionalConnectionFields: [ConnectionField] = [ ConnectionField( diff --git a/Plugins/RedisDriverPlugin/Info.plist b/Plugins/RedisDriverPlugin/Info.plist index 2d7c7ce9..57a55450 100644 --- a/Plugins/RedisDriverPlugin/Info.plist +++ b/Plugins/RedisDriverPlugin/Info.plist @@ -18,6 +18,8 @@ $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) + TableProPluginKitVersion + 1 NSPrincipalClass $(PRODUCT_MODULE_NAME).RedisPlugin diff --git a/Plugins/RedisDriverPlugin/RedisPlugin.swift b/Plugins/RedisDriverPlugin/RedisPlugin.swift index 2a91c02c..007dea01 100644 --- a/Plugins/RedisDriverPlugin/RedisPlugin.swift +++ b/Plugins/RedisDriverPlugin/RedisPlugin.swift @@ -19,7 +19,7 @@ final class RedisPlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "Redis" static let databaseDisplayName = "Redis" - static let iconName = "cylinder.fill" + static let iconName = "redis-icon" static let defaultPort = 6379 static let additionalConnectionFields: [ConnectionField] = [ ConnectionField( diff --git a/Plugins/SQLiteDriverPlugin/SQLitePlugin.swift b/Plugins/SQLiteDriverPlugin/SQLitePlugin.swift index d6ed628e..67a31892 100644 --- a/Plugins/SQLiteDriverPlugin/SQLitePlugin.swift +++ b/Plugins/SQLiteDriverPlugin/SQLitePlugin.swift @@ -16,7 +16,7 @@ final class SQLitePlugin: NSObject, TableProPlugin, DriverPlugin { static let databaseTypeId = "SQLite" static let databaseDisplayName = "SQLite" - static let iconName = "doc.fill" + static let iconName = "sqlite-icon" static let defaultPort = 0 // MARK: - UI/Capability Metadata @@ -24,7 +24,7 @@ final class SQLitePlugin: NSObject, TableProPlugin, DriverPlugin { static let requiresAuthentication = false static let supportsSSH = false static let supportsSSL = false - static let isDownloadable = true + static let isDownloadable = false static let pathFieldRole: PathFieldRole = .filePath static let connectionMode: ConnectionMode = .fileBased static let urlSchemes: [String] = ["sqlite"] diff --git a/TablePro.xcodeproj/project.pbxproj b/TablePro.xcodeproj/project.pbxproj index 9246c9c1..3c63afc3 100644 --- a/TablePro.xcodeproj/project.pbxproj +++ b/TablePro.xcodeproj/project.pbxproj @@ -35,7 +35,7 @@ 5ACE00012F4F000000000006 /* CodeEditTextView in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACE00012F4F000000000007 /* CodeEditTextView */; }; 5ACE00012F4F00000000000A /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACE00012F4F000000000009 /* Sparkle */; }; 5ACE00012F4F00000000000D /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5ACE00012F4F00000000000C /* MarkdownUI */; }; - 5AEA8B302F6808270040461A /* EtcdDriverPlugin.tableplugin in Copy Plug-Ins */ = {isa = PBXBuildFile; fileRef = 5AEA8B2A2F6808270040461A /* EtcdDriverPlugin.tableplugin */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 5AEA8B422F6808CA0040461A /* EtcdStatementGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA8B402F6808CA0040461A /* EtcdStatementGenerator.swift */; }; 5AEA8B432F6808CA0040461A /* EtcdPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA8B3D2F6808CA0040461A /* EtcdPlugin.swift */; }; 5AEA8B442F6808CA0040461A /* EtcdCommandParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5AEA8B3B2F6808CA0040461A /* EtcdCommandParser.swift */; }; @@ -153,7 +153,6 @@ 5A86A000D00000000 /* CSVExport.tableplugin in Copy Plug-Ins */, 5A86B000D00000000 /* JSONExport.tableplugin in Copy Plug-Ins */, 5A86C000D00000000 /* SQLExport.tableplugin in Copy Plug-Ins */, - 5AEA8B302F6808270040461A /* EtcdDriverPlugin.tableplugin in Copy Plug-Ins */, ); name = "Copy Plug-Ins"; runOnlyForDeploymentPostprocessing = 0; @@ -769,7 +768,6 @@ 5A86A000C00000000 /* PBXTargetDependency */, 5A86B000C00000000 /* PBXTargetDependency */, 5A86C000C00000000 /* PBXTargetDependency */, - 5AEA8B322F6808270040461A /* PBXTargetDependency */, ); fileSystemSynchronizedGroups = ( 5A1091C92EF17EDC0055EA7C /* TablePro */, diff --git a/TablePro/AppDelegate+WindowConfig.swift b/TablePro/AppDelegate+WindowConfig.swift index c2320983..4a9f884d 100644 --- a/TablePro/AppDelegate+WindowConfig.swift +++ b/TablePro/AppDelegate+WindowConfig.swift @@ -38,7 +38,10 @@ extension AppDelegate { ) item.target = self item.representedObject = connection.id - if let original = NSImage(named: connection.type.iconName) { + let iconName = connection.type.iconName + let original = NSImage(systemSymbolName: iconName, accessibilityDescription: nil) + ?? NSImage(named: iconName) + if let original { let resized = NSImage(size: NSSize(width: 16, height: 16), flipped: false) { rect in original.draw(in: rect) return true diff --git a/TablePro/Core/Plugins/PluginManager.swift b/TablePro/Core/Plugins/PluginManager.swift index 7d194ec3..f374222a 100644 --- a/TablePro/Core/Plugins/PluginManager.swift +++ b/TablePro/Core/Plugins/PluginManager.swift @@ -249,6 +249,7 @@ final class PluginManager { try validateDriverDescriptor(type(of: driver), pluginId: pluginId) } catch { Self.logger.error("Plugin '\(pluginId)' driver rejected: \(error.localizedDescription)") + return } if !driverPlugins.keys.contains(type(of: driver).databaseTypeId) { let driverType = type(of: driver) diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry.swift b/TablePro/Core/Plugins/PluginMetadataRegistry.swift index f5051078..480b1747 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry.swift @@ -594,9 +594,15 @@ final class PluginMetadataRegistry: @unchecked Sendable { let schemes = driverType.urlSchemes let primaryScheme = schemes.first ?? driverType.databaseTypeId.lowercased() + // Prefer the registry default's custom icon (e.g. "mysql-icon") over the + // plugin's generic SF Symbol (e.g. "cylinder.fill"). The registry defaults + // have curated SVG icons in the asset catalog. + let existingIcon = snapshot(forTypeId: driverType.databaseTypeId)?.iconName + let iconName = existingIcon ?? driverType.iconName + return PluginMetadataSnapshot( displayName: driverType.databaseDisplayName, - iconName: driverType.iconName, + iconName: iconName, defaultPort: driverType.defaultPort, requiresAuthentication: driverType.requiresAuthentication, supportsForeignKeys: driverType.supportsForeignKeys, diff --git a/TablePro/Models/Connection/DatabaseConnection.swift b/TablePro/Models/Connection/DatabaseConnection.swift index d6b61ac8..cb871b9a 100644 --- a/TablePro/Models/Connection/DatabaseConnection.swift +++ b/TablePro/Models/Connection/DatabaseConnection.swift @@ -282,6 +282,16 @@ extension DatabaseType { PluginMetadataRegistry.shared.snapshot(forTypeId: pluginTypeId)?.iconName ?? "database-icon" } + /// Returns the correct SwiftUI Image for this database type, handling both + /// SF Symbol names (e.g. "cylinder.fill") and asset catalog names (e.g. "mysql-icon"). + var iconImage: Image { + let name = iconName + if NSImage(systemSymbolName: name, accessibilityDescription: nil) != nil { + return Image(systemName: name) + } + return Image(name) + } + var defaultPort: Int { PluginMetadataRegistry.shared.snapshot(forTypeId: pluginTypeId)?.defaultPort ?? 0 } diff --git a/TablePro/Views/Connection/ConnectionFormView.swift b/TablePro/Views/Connection/ConnectionFormView.swift index c168fdcf..8e6583d1 100644 --- a/TablePro/Views/Connection/ConnectionFormView.swift +++ b/TablePro/Views/Connection/ConnectionFormView.swift @@ -233,7 +233,7 @@ struct ConnectionFormView: View { // swiftlint:disable:this type_body_length } } } icon: { - Image(t.iconName) + t.iconImage .resizable() .aspectRatio(contentMode: .fit) .frame(width: 20, height: 20) diff --git a/TablePro/Views/Connection/ConnectionSidebarHeader.swift b/TablePro/Views/Connection/ConnectionSidebarHeader.swift index 6ad1debf..a9772a84 100644 --- a/TablePro/Views/Connection/ConnectionSidebarHeader.swift +++ b/TablePro/Views/Connection/ConnectionSidebarHeader.swift @@ -34,7 +34,7 @@ struct ConnectionSidebarHeader: View { onSelectSession(session.id) }) { HStack { - Image(session.connection.type.iconName) + session.connection.type.iconImage .renderingMode(.template) .foregroundStyle(session.connection.displayColor) @@ -67,7 +67,7 @@ struct ConnectionSidebarHeader: View { onOpenConnection(connection) }) { HStack { - Image(connection.type.iconName) + connection.type.iconImage .renderingMode(.template) .foregroundStyle(connection.displayColor) @@ -90,7 +90,7 @@ struct ConnectionSidebarHeader: View { HStack(spacing: 8) { // Database icon if let session = currentSession { - Image(session.connection.type.iconName) + session.connection.type.iconImage .renderingMode(.template) .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.medium)) .foregroundStyle(session.connection.displayColor) diff --git a/TablePro/Views/Connection/WelcomeWindowView.swift b/TablePro/Views/Connection/WelcomeWindowView.swift index 54cc02fd..70eda06a 100644 --- a/TablePro/Views/Connection/WelcomeWindowView.swift +++ b/TablePro/Views/Connection/WelcomeWindowView.swift @@ -649,7 +649,7 @@ private struct ConnectionRow: View { var body: some View { HStack(spacing: 12) { // Database type icon - Image(connection.type.iconName) + connection.type.iconImage .renderingMode(.template) .font(.system(size: ThemeEngine.shared.activeTheme.iconSizes.medium)) .foregroundStyle(connection.displayColor) From a7083ccbb7a5a0a6f5db67adb5999bf0ed8b7bdf Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 17 Mar 2026 09:02:04 +0700 Subject: [PATCH 2/3] feat: pre-apply FK filter when navigating to referenced table --- .../Infrastructure/SessionStateFactory.swift | 4 ++++ TablePro/Models/Database/TableFilter.swift | 4 ++-- TablePro/Models/Query/EditorTabPayload.swift | 8 +++++++- TablePro/Resources/Localizable.xcstrings | 10 ++++++++++ .../MainContentCoordinator+FKNavigation.swift | 10 +++++++++- TablePro/Views/Main/MainContentView.swift | 13 +++++++++++++ 6 files changed, 45 insertions(+), 4 deletions(-) diff --git a/TablePro/Core/Services/Infrastructure/SessionStateFactory.swift b/TablePro/Core/Services/Infrastructure/SessionStateFactory.swift index 47106a1b..122ae1ae 100644 --- a/TablePro/Core/Services/Infrastructure/SessionStateFactory.swift +++ b/TablePro/Core/Services/Infrastructure/SessionStateFactory.swift @@ -71,6 +71,10 @@ enum SessionStateFactory { if payload.showStructure { tabMgr.tabs[index].showStructure = true } + if let initialFilter = payload.initialFilterState { + tabMgr.tabs[index].filterState = initialFilter + filterMgr.restoreFromTabState(initialFilter) + } } } else { tabMgr.addTab(databaseName: payload.databaseName ?? connection.database) diff --git a/TablePro/Models/Database/TableFilter.swift b/TablePro/Models/Database/TableFilter.swift index bdf17b6b..d1b09a58 100644 --- a/TablePro/Models/Database/TableFilter.swift +++ b/TablePro/Models/Database/TableFilter.swift @@ -71,7 +71,7 @@ enum FilterOperator: String, CaseIterable, Identifiable, Codable { } /// Represents a single table filter condition -struct TableFilter: Identifiable, Equatable, Codable { +struct TableFilter: Identifiable, Equatable, Hashable, Codable { let id: UUID var columnName: String // Column to filter on, or "__RAW__" for raw SQL var filterOperator: FilterOperator @@ -151,7 +151,7 @@ struct TableFilter: Identifiable, Equatable, Codable { } /// Stores per-tab filter state (preserves filters when switching tabs) -struct TabFilterState: Equatable, Codable { +struct TabFilterState: Equatable, Hashable, Codable { var filters: [TableFilter] var appliedFilters: [TableFilter] var isVisible: Bool diff --git a/TablePro/Models/Query/EditorTabPayload.swift b/TablePro/Models/Query/EditorTabPayload.swift index f2c3c32a..85e8243b 100644 --- a/TablePro/Models/Query/EditorTabPayload.swift +++ b/TablePro/Models/Query/EditorTabPayload.swift @@ -31,6 +31,8 @@ internal struct EditorTabPayload: Codable, Hashable { internal let skipAutoExecute: Bool /// Whether this tab is a preview (temporary) tab internal let isPreview: Bool + /// Initial filter state (for FK navigation — pre-applies a WHERE filter) + internal let initialFilterState: TabFilterState? internal init( id: UUID = UUID(), @@ -42,7 +44,8 @@ internal struct EditorTabPayload: Codable, Hashable { isView: Bool = false, showStructure: Bool = false, skipAutoExecute: Bool = false, - isPreview: Bool = false + isPreview: Bool = false, + initialFilterState: TabFilterState? = nil ) { self.id = id self.connectionId = connectionId @@ -54,6 +57,7 @@ internal struct EditorTabPayload: Codable, Hashable { self.showStructure = showStructure self.skipAutoExecute = skipAutoExecute self.isPreview = isPreview + self.initialFilterState = initialFilterState } internal init(from decoder: Decoder) throws { @@ -68,6 +72,7 @@ internal struct EditorTabPayload: Codable, Hashable { showStructure = try container.decodeIfPresent(Bool.self, forKey: .showStructure) ?? false skipAutoExecute = try container.decodeIfPresent(Bool.self, forKey: .skipAutoExecute) ?? false isPreview = try container.decodeIfPresent(Bool.self, forKey: .isPreview) ?? false + initialFilterState = try container.decodeIfPresent(TabFilterState.self, forKey: .initialFilterState) } /// Whether this payload is a "connection-only" payload — just a connectionId @@ -89,5 +94,6 @@ internal struct EditorTabPayload: Codable, Hashable { self.showStructure = tab.showStructure self.skipAutoExecute = skipAutoExecute self.isPreview = false + self.initialFilterState = nil } } diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 24ee836c..7a6496bd 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -13360,6 +13360,16 @@ } } }, + "Plugin was built with PluginKit version %lld, but version %lld or later is required. Please update the plugin." : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plugin was built with PluginKit version %1$lld, but version %2$lld or later is required. Please update the plugin." + } + } + } + }, "Plugins" : { "localizations" : { "vi" : { diff --git a/TablePro/Views/Main/Extensions/MainContentCoordinator+FKNavigation.swift b/TablePro/Views/Main/Extensions/MainContentCoordinator+FKNavigation.swift index 67e6679d..dc8b2807 100644 --- a/TablePro/Views/Main/Extensions/MainContentCoordinator+FKNavigation.swift +++ b/TablePro/Views/Main/Extensions/MainContentCoordinator+FKNavigation.swift @@ -52,12 +52,20 @@ extension MainContentCoordinator { // If current tab has unsaved changes, open in a new native tab instead of replacing if changeManager.hasChanges { + let fkFilterState = TabFilterState( + filters: [filter], + appliedFilters: [filter], + isVisible: true, + quickSearchText: "", + filterLogicMode: .and + ) let payload = EditorTabPayload( connectionId: connection.id, tabType: .table, tableName: referencedTable, databaseName: currentDatabase, - isView: false + isView: false, + initialFilterState: fkFilterState ) WindowOpener.shared.openNativeTab(payload) return diff --git a/TablePro/Views/Main/MainContentView.swift b/TablePro/Views/Main/MainContentView.swift index b86cc64b..9071b90b 100644 --- a/TablePro/Views/Main/MainContentView.swift +++ b/TablePro/Views/Main/MainContentView.swift @@ -487,6 +487,19 @@ struct MainContentView: View { { Task { await coordinator.switchDatabase(to: selectedTab.databaseName) } } else { + if !selectedTab.filterState.appliedFilters.isEmpty, + let tableName = selectedTab.tableName, + let tabIndex = tabManager.selectedTabIndex + { + let filteredQuery = coordinator.queryBuilder.buildFilteredQuery( + tableName: tableName, + filters: selectedTab.filterState.appliedFilters, + columns: selectedTab.resultColumns, + limit: selectedTab.pagination.pageSize, + offset: selectedTab.pagination.currentOffset + ) + tabManager.tabs[tabIndex].query = filteredQuery + } coordinator.executeTableTabQueryDirectly() } } else { From f231b33a02cf557a73383b94742693cce0890cb8 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Tue, 17 Mar 2026 09:07:38 +0700 Subject: [PATCH 3/3] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20remov?= =?UTF-8?q?e=20orphaned=20xcstring,=20atomic=20icon=20preservation,=20clar?= =?UTF-8?q?ify=20columns?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TablePro/Core/Plugins/PluginManager.swift | 4 +-- .../Core/Plugins/PluginMetadataRegistry.swift | 34 +++++++++++++------ TablePro/Resources/Localizable.xcstrings | 10 ------ TablePro/Views/Main/MainContentView.swift | 3 +- 4 files changed, 28 insertions(+), 23 deletions(-) diff --git a/TablePro/Core/Plugins/PluginManager.swift b/TablePro/Core/Plugins/PluginManager.swift index f374222a..3692b7b7 100644 --- a/TablePro/Core/Plugins/PluginManager.swift +++ b/TablePro/Core/Plugins/PluginManager.swift @@ -265,9 +265,9 @@ final class PluginManager { from: driverType, isDownloadable: driverType.isDownloadable ) - PluginMetadataRegistry.shared.register(snapshot: snapshot, forTypeId: typeId) + PluginMetadataRegistry.shared.register(snapshot: snapshot, forTypeId: typeId, preserveIcon: true) for additionalId in driverType.additionalDatabaseTypeIds { - PluginMetadataRegistry.shared.register(snapshot: snapshot, forTypeId: additionalId) + PluginMetadataRegistry.shared.register(snapshot: snapshot, forTypeId: additionalId, preserveIcon: true) PluginMetadataRegistry.shared.registerTypeAlias(additionalId, primaryTypeId: typeId) } diff --git a/TablePro/Core/Plugins/PluginMetadataRegistry.swift b/TablePro/Core/Plugins/PluginMetadataRegistry.swift index 480b1747..4f7d5f6f 100644 --- a/TablePro/Core/Plugins/PluginMetadataRegistry.swift +++ b/TablePro/Core/Plugins/PluginMetadataRegistry.swift @@ -117,6 +117,22 @@ struct PluginMetadataSnapshot: Sendable { additionalConnectionFields: [] ) } + + func withIconName(_ newIconName: String) -> PluginMetadataSnapshot { + PluginMetadataSnapshot( + displayName: displayName, iconName: newIconName, defaultPort: defaultPort, + requiresAuthentication: requiresAuthentication, supportsForeignKeys: supportsForeignKeys, + supportsSchemaEditing: supportsSchemaEditing, isDownloadable: isDownloadable, + primaryUrlScheme: primaryUrlScheme, parameterStyle: parameterStyle, + navigationModel: navigationModel, explainVariants: explainVariants, + pathFieldRole: pathFieldRole, supportsHealthMonitor: supportsHealthMonitor, + urlSchemes: urlSchemes, postConnectActions: postConnectActions, + brandColorHex: brandColorHex, queryLanguageName: queryLanguageName, + editorLanguage: editorLanguage, connectionMode: connectionMode, + supportsDatabaseSwitching: supportsDatabaseSwitching, + capabilities: capabilities, schema: schema, editor: editor, connection: connection + ) + } } final class PluginMetadataRegistry: @unchecked Sendable { @@ -513,11 +529,15 @@ final class PluginMetadataRegistry: @unchecked Sendable { reverseTypeIndex["ScyllaDB"] = "Cassandra" } - func register(snapshot: PluginMetadataSnapshot, forTypeId typeId: String) { + func register(snapshot: PluginMetadataSnapshot, forTypeId typeId: String, preserveIcon: Bool = false) { lock.lock() defer { lock.unlock() } - snapshots[typeId] = snapshot - for scheme in snapshot.urlSchemes { + var resolved = snapshot + if preserveIcon, let existingIcon = snapshots[typeId]?.iconName { + resolved = snapshot.withIconName(existingIcon) + } + snapshots[typeId] = resolved + for scheme in resolved.urlSchemes { schemeIndex[scheme.lowercased()] = typeId } } @@ -594,15 +614,9 @@ final class PluginMetadataRegistry: @unchecked Sendable { let schemes = driverType.urlSchemes let primaryScheme = schemes.first ?? driverType.databaseTypeId.lowercased() - // Prefer the registry default's custom icon (e.g. "mysql-icon") over the - // plugin's generic SF Symbol (e.g. "cylinder.fill"). The registry defaults - // have curated SVG icons in the asset catalog. - let existingIcon = snapshot(forTypeId: driverType.databaseTypeId)?.iconName - let iconName = existingIcon ?? driverType.iconName - return PluginMetadataSnapshot( displayName: driverType.databaseDisplayName, - iconName: iconName, + iconName: driverType.iconName, defaultPort: driverType.defaultPort, requiresAuthentication: driverType.requiresAuthentication, supportsForeignKeys: driverType.supportsForeignKeys, diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 7a6496bd..24ee836c 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -13360,16 +13360,6 @@ } } }, - "Plugin was built with PluginKit version %lld, but version %lld or later is required. Please update the plugin." : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Plugin was built with PluginKit version %1$lld, but version %2$lld or later is required. Please update the plugin." - } - } - } - }, "Plugins" : { "localizations" : { "vi" : { diff --git a/TablePro/Views/Main/MainContentView.swift b/TablePro/Views/Main/MainContentView.swift index 9071b90b..095fd115 100644 --- a/TablePro/Views/Main/MainContentView.swift +++ b/TablePro/Views/Main/MainContentView.swift @@ -491,10 +491,11 @@ struct MainContentView: View { let tableName = selectedTab.tableName, let tabIndex = tabManager.selectedTabIndex { + // columns is [] on initial load — buildFilteredQuery uses SELECT * let filteredQuery = coordinator.queryBuilder.buildFilteredQuery( tableName: tableName, filters: selectedTab.filterState.appliedFilters, - columns: selectedTab.resultColumns, + columns: [], limit: selectedTab.pagination.pageSize, offset: selectedTab.pagination.currentOffset )