diff --git a/CHANGELOG.md b/CHANGELOG.md index b7212daf6..76d153730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Toolbar connection identity now leads with the database engine icon tinted with the connection's color, followed by the connection name, so multiple windows targeting the same database (e.g. `prod-safe`, `prod-unsafe`, `staging`, `local` against the same Postgres) are distinguishable at a glance instead of all reading "PostgreSQL 16.x". The pattern matches Calendar.app and Reminders.app, where a single icon carries both the type (shape) and the user-chosen identity (color). When no custom Connection Color is set, the icon falls back to the database type's brand color via the existing `displayColor` resolver, so the brand identity is preserved by default. The icon scales with Dynamic Type via `@ScaledMetric`. The verbose "PostgreSQL 16.1" text moves to the hover tooltip and the VoiceOver label, which reads "Connection: prod-safe, PostgreSQL 16.1". Both the connection-name and database-name `Text` labels now use `.fixedSize(horizontal: true, vertical: false)` so SwiftUI no longer compresses them inside the `NSHostingController` principal item. Previously a long database name would render truncated as `sep...` even in a 1900pt-wide window because SwiftUI compressed flexible text before NSToolbar got a chance to evaluate intrinsic width. Fixes #1044. - All Safe Mode levels are now free. **Safe Mode** (Touch ID), **Safe Mode (Full)**, and **Read Only** no longer require a Pro license. Removed the Pro gate from `SafeModeGuard` (no more silent downgrade to Silent on unlicensed Macs), the `(Pro)` suffix and license prompt from the Customization pane Picker and toolbar `SafeModeBadgeView`, and the `safeMode` case from `ProFeature`. The `requiresPro` computed property on `SafeModeLevel` is gone along with the unused `showSafeModeProAlert` / `showActivationSheet` flags on `CustomizationPaneViewModel`. - Favorites sidebar state is now connection-scoped, not window-scoped. Opening a second native tab for the same connection no longer reloads the favorites tree from SQLite or flashes a spinner. The folders/favorites/linked-files cache (`ConnectionDataCache`) is shared across windows of the same connection and refreshes on `.sqlFavoritesDidUpdate` and `.linkedSQLFoldersDidUpdate`. Favorite selection (`ConnectionSidebarState.selectedFavoriteNodeId`) is also shared, so highlighting a favorite in window A reflects in window B and persists across launches via UserDefaults. Favorites search text remains per-window (`WindowSidebarState.favoritesSearchText`), matching Mail/Notes patterns where each window can search independently. The single sidebar `NSSearchField` routes to the connection-shared text on Tables and to the window-local text on Favorites based on the active tab. - Connection Form rebuilt around macOS HIG sidebar navigation. The old segmented-tab form (~2200 lines across five files) is replaced by a `NavigationSplitView` with five sidebar panes (General, SSH Tunnel, SSL/TLS, Customization, Advanced). State previously held in 30+ flat `@State` vars is now split across six `@Observable` per-pane view models behind a `ConnectionFormCoordinator`. Plugin-driven additional fields auto-route to the right pane by their declared `FieldSection`. The toolbar exposes Cancel, Save, and Save & Connect natively; Test Connection lives inline in the General pane as a Status row. Each sidebar item shows a red warning triangle when its pane has missing required fields. diff --git a/TablePro/Views/Toolbar/ConnectionStatusView.swift b/TablePro/Views/Toolbar/ConnectionStatusView.swift index 88fc775c5..fa6d9f27a 100644 --- a/TablePro/Views/Toolbar/ConnectionStatusView.swift +++ b/TablePro/Views/Toolbar/ConnectionStatusView.swift @@ -14,23 +14,20 @@ struct ConnectionStatusView: View { let databaseVersion: String? let databaseName: String let connectionName: String - let connectionState: ToolbarConnectionState let displayColor: Color - let tagName: String? var safeModeLevel: SafeModeLevel = .silent var onSwitchDatabase: (() -> Void)? + @ScaledMetric private var engineIconSize: CGFloat = 14 + var body: some View { HStack(spacing: 10) { - // Database type icon + version - databaseInfoSection - - // Vertical separator - Divider() - .frame(height: 12) + connectionIdentitySection - // Database name (clickable to switch databases) if !databaseName.isEmpty { + Divider() + .frame(height: 12) + databaseNameSection } } @@ -38,21 +35,25 @@ struct ConnectionStatusView: View { // MARK: - Subviews - /// Database type and version info - private var databaseInfoSection: some View { - Text(formattedDatabaseInfo) - .font(.system(.subheadline, design: .monospaced)) - .foregroundStyle(ThemeEngine.shared.colors.toolbar.secondaryTextSwiftUI) - .lineLimit(1) - .truncationMode(.middle) - .frame(maxWidth: 280) - .accessibilityLabel( - String(format: String(localized: "Database type: %@"), formattedDatabaseInfo) - ) - .help("Database: \(formattedDatabaseInfo)") + private var connectionIdentitySection: some View { + HStack(spacing: 6) { + databaseType.iconImage + .renderingMode(.template) + .foregroundStyle(displayColor) + .frame(width: engineIconSize, height: engineIconSize) + + Text(connectionName) + .font(.callout.weight(.medium)) + .foregroundStyle(.primary) + .lineLimit(1) + .truncationMode(.tail) + .fixedSize(horizontal: true, vertical: false) + } + .help(connectionTooltip) + .accessibilityElement(children: .combine) + .accessibilityLabel(connectionAccessibilityLabel) } - /// Database name (clickable to open database switcher, plain label for SQLite) @ViewBuilder private var databaseNameSection: some View { if !PluginManager.shared.supportsDatabaseSwitching(for: databaseType) { @@ -80,6 +81,8 @@ struct ConnectionStatusView: View { Text(databaseName) .font(.callout.weight(.medium)) .foregroundStyle(.primary) + .lineLimit(1) + .fixedSize(horizontal: true, vertical: false) } } @@ -91,62 +94,62 @@ struct ConnectionStatusView: View { } return databaseType.rawValue } + + private var connectionTooltip: String { + String(format: String(localized: "%@ • %@"), connectionName, formattedDatabaseInfo) + } + + private var connectionAccessibilityLabel: String { + String(format: String(localized: "Connection: %@, %@"), connectionName, formattedDatabaseInfo) + } } // MARK: - Preview -#Preview("Connected") { +#Preview("MariaDB") { ConnectionStatusView( databaseType: .mariadb, databaseVersion: "11.1.2", databaseName: "production_db", connectionName: "Production Database", - connectionState: .connected, - displayColor: .cyan, - tagName: "production" + displayColor: .cyan ) .padding() .background(Color(nsColor: .windowBackgroundColor)) } -#Preview("Executing - No Duplicate") { +#Preview("MySQL") { ConnectionStatusView( databaseType: .mysql, databaseVersion: "8.0.35", databaseName: "dev_db", connectionName: "Development", - connectionState: .executing, - displayColor: .orange, - tagName: "local" + displayColor: .orange ) .padding() .background(Color(nsColor: .windowBackgroundColor)) } -#Preview("No Tag") { +#Preview("PostgreSQL Dark") { ConnectionStatusView( databaseType: .postgresql, databaseVersion: "16.1", databaseName: "analytics", connectionName: "Analytics DB", - connectionState: .connected, - displayColor: .blue, - tagName: nil + displayColor: .blue ) .padding() .background(Color(nsColor: .windowBackgroundColor)) .preferredColorScheme(.dark) } -#Preview("Duplicate Name") { +#Preview("Empty Database") { ConnectionStatusView( databaseType: .mysql, databaseVersion: "9.5.0", - databaseName: "laravel", + databaseName: "", connectionName: "Local", - connectionState: .connected, - displayColor: .green, - tagName: "local" + displayColor: .green ) .padding() .background(Color(nsColor: .windowBackgroundColor)) diff --git a/TablePro/Views/Toolbar/TableProToolbarView.swift b/TablePro/Views/Toolbar/TableProToolbarView.swift index 7972bc777..4e045781b 100644 --- a/TablePro/Views/Toolbar/TableProToolbarView.swift +++ b/TablePro/Views/Toolbar/TableProToolbarView.swift @@ -34,9 +34,7 @@ struct ToolbarPrincipalContent: View { databaseVersion: state.databaseVersion, databaseName: state.databaseName, connectionName: state.connectionName, - connectionState: state.connectionState, displayColor: state.displayColor, - tagName: tag?.name, safeModeLevel: state.safeModeLevel, onSwitchDatabase: onSwitchDatabase )