From 54b7a27d109802cbed01962c523a4b2ec1b980c5 Mon Sep 17 00:00:00 2001 From: Ngo Quoc Dat Date: Wed, 6 May 2026 23:22:57 +0700 Subject: [PATCH] chore: remove xlsx export pro gate --- CHANGELOG.md | 1 + TablePro/Models/Settings/ProFeature.swift | 7 ---- TablePro/Resources/Localizable.xcstrings | 44 ----------------------- TablePro/Views/Export/ExportDialog.swift | 29 ++------------- docs/features/import-export.mdx | 4 --- 5 files changed, 4 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec19dff0f..6a4b0ffc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,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. +- XLSX export is now free for everyone. Removed the Pro gate from the Export dialog: the `(Pro)` suffix in the format Picker, the orange "XLSX export requires a Pro license." caption, the `Activate License...` link, and the `isExportDisabled` short-circuit on unlicensed Macs are gone. The supporting machinery (`proFormatIds` set, `isProGatedFormat(_:)` predicate, `showActivationSheet` `@State` and its `.sheet` modifier in `ExportDialog`, the `xlsxExport` case in `ProFeature` plus its three switch arms, and the docs Note in `import-export.mdx`) is removed too. License-state changes no longer affect Excel export at all. - 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/Models/Settings/ProFeature.swift b/TablePro/Models/Settings/ProFeature.swift index f495ad015..db7a1cb5b 100644 --- a/TablePro/Models/Settings/ProFeature.swift +++ b/TablePro/Models/Settings/ProFeature.swift @@ -10,7 +10,6 @@ import Foundation /// Features that require a Pro (active) license internal enum ProFeature: String, CaseIterable { case iCloudSync - case xlsxExport case encryptedExport case envVarReferences case linkedFolders @@ -19,8 +18,6 @@ internal enum ProFeature: String, CaseIterable { switch self { case .iCloudSync: return String(localized: "iCloud Sync") - case .xlsxExport: - return String(localized: "XLSX Export") case .encryptedExport: return String(localized: "Encrypted Export") case .envVarReferences: @@ -34,8 +31,6 @@ internal enum ProFeature: String, CaseIterable { switch self { case .iCloudSync: return "icloud" - case .xlsxExport: - return "tablecells" case .encryptedExport: return "lock.doc" case .envVarReferences: @@ -49,8 +44,6 @@ internal enum ProFeature: String, CaseIterable { switch self { case .iCloudSync: return String(localized: "Sync connections, settings, and history across your Macs.") - case .xlsxExport: - return String(localized: "Export query results and tables to Excel format.") case .encryptedExport: return String(localized: "Export connections with encrypted credentials.") case .envVarReferences: diff --git a/TablePro/Resources/Localizable.xcstrings b/TablePro/Resources/Localizable.xcstrings index 6fcf54439..213174546 100644 --- a/TablePro/Resources/Localizable.xcstrings +++ b/TablePro/Resources/Localizable.xcstrings @@ -51363,50 +51363,6 @@ } } }, - "XLSX Export" : { - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "XLSX Dışa Aktarma" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xuất XLSX" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "XLSX 导出" - } - } - } - }, - "XLSX export requires a Pro license." : { - "localizations" : { - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "XLSX dışa aktarma için Pro lisans gereklidir." - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Xuất XLSX yêu cầu giấy phép Pro." - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "XLSX 导出需要 Pro 许可证。" - } - } - } - }, "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" : { "localizations" : { "tr" : { diff --git a/TablePro/Views/Export/ExportDialog.swift b/TablePro/Views/Export/ExportDialog.swift index 44f0c962e..4c9cb2529 100644 --- a/TablePro/Views/Export/ExportDialog.swift +++ b/TablePro/Views/Export/ExportDialog.swift @@ -26,7 +26,6 @@ struct ExportDialog: View { @State private var showProgressDialog = false @State private var showSuccessDialog = false @State private var exportedFileURL: URL? - @State private var showActivationSheet = false // MARK: - User Preferences @@ -94,9 +93,6 @@ struct ExportDialog: View { } .frame(width: dialogWidth) .background(Color(nsColor: .windowBackgroundColor)) - .sheet(isPresented: $showActivationSheet) { - LicenseActivationSheet() - } .onAppear { let available = availableFormats if !available.contains(where: { type(of: $0).formatId == config.formatId }) { @@ -281,11 +277,7 @@ struct ExportDialog: View { Picker("", selection: $config.formatId) { ForEach(availableFormatIds, id: \.self) { formatId in if let plugin = PluginManager.shared.exportPlugin(forFormat: formatId) { - if isProGatedFormat(formatId) { - Text("\(type(of: plugin).formatDisplayName) (Pro)").tag(formatId) - } else { - Text(type(of: plugin).formatDisplayName).tag(formatId) - } + Text(type(of: plugin).formatDisplayName).tag(formatId) } } } @@ -302,18 +294,8 @@ struct ExportDialog: View { } } - // Selection count or Pro gate message VStack(spacing: 2) { - if isProGatedFormat(config.formatId) { - Text(String(localized: "XLSX export requires a Pro license.")) - .font(.subheadline) - .foregroundStyle(Color(nsColor: .systemOrange)) - Button(String(localized: "Activate License...")) { - showActivationSheet = true - } - .font(.subheadline) - .buttonStyle(.link) - } else if case .streamingQuery = mode { + if case .streamingQuery = mode { Text("All rows") .font(.subheadline) .foregroundStyle(.secondary) @@ -450,7 +432,7 @@ struct ExportDialog: View { } private var isExportDisabled: Bool { - if isExporting || !isFileNameValid || availableFormats.isEmpty || isProGatedFormat(config.formatId) { + if isExporting || !isFileNameValid || availableFormats.isEmpty { return true } if case .streamingQuery = mode { @@ -463,7 +445,6 @@ struct ExportDialog: View { } private static let formatDisplayOrder = ["csv", "json", "sql", "xlsx", "mql"] - private static let proFormatIds: Set = ["xlsx"] private func formatDescription(for formatId: String) -> String { switch formatId { @@ -476,10 +457,6 @@ struct ExportDialog: View { } } - private func isProGatedFormat(_ formatId: String) -> Bool { - Self.proFormatIds.contains(formatId) && !LicenseManager.shared.isFeatureAvailable(.xlsxExport) - } - /// Windows reserved device names (case-insensitive) private static let windowsReservedNames: Set = [ "CON", "PRN", "AUX", "NUL", diff --git a/docs/features/import-export.mdx b/docs/features/import-export.mdx index 9e838076e..f46ad27b9 100644 --- a/docs/features/import-export.mdx +++ b/docs/features/import-export.mdx @@ -134,10 +134,6 @@ Export data in five formats (CSV, JSON, SQL, MQL, XLSX), import SQL files with g ``` - - XLSX export requires a **Pro license**. - - | Option | Default | |--------|---------| | Include headers (bold first row) | Yes |