Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Plugins/CassandraDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>TableProPluginKitVersion</key>
<integer>1</integer>
<key>NSPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).CassandraPlugin</string>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/ClickHouseDriverPlugin/ClickHousePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion Plugins/EtcdDriverPlugin/EtcdPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MSSQLDriverPlugin/MSSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MongoDBDriverPlugin/MongoDBPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 1 addition & 1 deletion Plugins/MySQLDriverPlugin/MySQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion Plugins/OracleDriverPlugin/OraclePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 1 addition & 1 deletion Plugins/PostgreSQLDriverPlugin/PostgreSQLPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions Plugins/RedisDriverPlugin/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>TableProPluginKitVersion</key>
<integer>1</integer>
<key>NSPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).RedisPlugin</string>
</dict>
Expand Down
2 changes: 1 addition & 1 deletion Plugins/RedisDriverPlugin/RedisPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions Plugins/SQLiteDriverPlugin/SQLitePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ 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

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"]
Expand Down
4 changes: 1 addition & 3 deletions TablePro.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -769,7 +768,6 @@
5A86A000C00000000 /* PBXTargetDependency */,
5A86B000C00000000 /* PBXTargetDependency */,
5A86C000C00000000 /* PBXTargetDependency */,
5AEA8B322F6808270040461A /* PBXTargetDependency */,
);
fileSystemSynchronizedGroups = (
5A1091C92EF17EDC0055EA7C /* TablePro */,
Expand Down
5 changes: 4 additions & 1 deletion TablePro/AppDelegate+WindowConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions TablePro/Core/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -264,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)
}

Expand Down
26 changes: 23 additions & 3 deletions TablePro/Core/Plugins/PluginMetadataRegistry.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions TablePro/Models/Connection/DatabaseConnection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
4 changes: 2 additions & 2 deletions TablePro/Models/Database/TableFilter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion TablePro/Models/Query/EditorTabPayload.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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
Expand All @@ -89,5 +94,6 @@ internal struct EditorTabPayload: Codable, Hashable {
self.showStructure = tab.showStructure
self.skipAutoExecute = skipAutoExecute
self.isPreview = false
self.initialFilterState = nil
}
}
2 changes: 1 addition & 1 deletion TablePro/Views/Connection/ConnectionFormView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 3 additions & 3 deletions TablePro/Views/Connection/ConnectionSidebarHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -67,7 +67,7 @@ struct ConnectionSidebarHeader: View {
onOpenConnection(connection)
}) {
HStack {
Image(connection.type.iconName)
connection.type.iconImage
.renderingMode(.template)
.foregroundStyle(connection.displayColor)

Expand All @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Views/Connection/WelcomeWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions TablePro/Views/Main/MainContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,20 @@ struct MainContentView: View {
{
Task { await coordinator.switchDatabase(to: selectedTab.databaseName) }
} else {
if !selectedTab.filterState.appliedFilters.isEmpty,
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: [],
limit: selectedTab.pagination.pageSize,
offset: selectedTab.pagination.currentOffset
)
tabManager.tabs[tabIndex].query = filteredQuery
}
coordinator.executeTableTabQueryDirectly()
}
} else {
Expand Down
Loading