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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
81 changes: 42 additions & 39 deletions TablePro/Views/Toolbar/ConnectionStatusView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,45 +14,46 @@ 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
}
}
}

// 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) {
Expand Down Expand Up @@ -80,6 +81,8 @@ struct ConnectionStatusView: View {
Text(databaseName)
.font(.callout.weight(.medium))
.foregroundStyle(.primary)
.lineLimit(1)
.fixedSize(horizontal: true, vertical: false)
}
}

Expand All @@ -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))
Expand Down
2 changes: 0 additions & 2 deletions TablePro/Views/Toolbar/TableProToolbarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Expand Down
Loading