Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
0594f9f
feat: redesign Plugins settings with HSplitView master-detail layout
datlechin Mar 13, 2026
1de2c0d
docs: add CHANGELOG entry and update plugin settings docs for HSplitV…
datlechin Mar 13, 2026
1972a6c
fix: address PR review feedback from CodeRabbit
datlechin Mar 13, 2026
e34177c
fix: clear needsRestart flag before early return guard in loadPending…
datlechin Mar 13, 2026
635b03b
refactor: replace HSplitView with NavigationSplitView for modern macO…
datlechin Mar 13, 2026
16a5435
fix: revert NavigationSplitView to HSplitView to fix settings layout
datlechin Mar 13, 2026
29a1eb3
refactor: move enable/disable toggle from list rows to detail pane
datlechin Mar 13, 2026
dc2134a
refactor: remove compact install button from browse list rows
datlechin Mar 13, 2026
2de2a64
feat: add search filter to installed plugins list
datlechin Mar 13, 2026
258ebb2
fix: show loading/empty/error states full-width in browse tab
datlechin Mar 13, 2026
e5bc453
fix: make empty/loading states fill available space in browse tab
datlechin Mar 13, 2026
48dd573
refactor: polish plugin settings UI layout and consistency
datlechin Mar 13, 2026
599345d
refactor: remove dead RegistryPluginRow and update docs for toggle lo…
datlechin Mar 13, 2026
4aac87a
merge: resolve conflicts with main for plugin settings hsplitview
datlechin Mar 14, 2026
818a202
feat: enrich plugin list rows with version, status badges, and downlo…
datlechin Mar 14, 2026
9d71e29
docs: update plugin settings docs for enriched rows (en, vi, zh)
datlechin Mar 14, 2026
7f76702
fix: address PR review — extract PluginIconView, fix disabled conditi…
datlechin Mar 14, 2026
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Redesigned Plugins settings tab with HSplitView master-detail layout: plugin list on the left, detail pane on the right, matching macOS conventions
- Redesigned Plugins settings tab with HSplitView master-detail layout: plugin list on the left, detail pane on the right, matching macOS conventions. Plugin rows now show version, author/capability, and install status at a glance
- Download counts in browse tab now always fetch latest from GitHub API (5-minute in-memory cooldown per session)
- Replaced ~40 hardcoded `DatabaseType` switches across ~20 UI files with dynamic plugin property lookups via `PluginManager`, so third-party plugins get correct UI behavior (colors, labels, editor language, feature toggles) automatically
- ConnectionFormView now fully dynamic: pgpass toggle, password visibility, and SSH/SSL tab visibility all driven by plugin metadata (`FieldSection`, `hidesPassword`, `supportsSSH`/`supportsSSL`) instead of hardcoded type checks
- Replaced `AppState.isMongoDB`/`isRedis` booleans with `AppState.editorLanguage: EditorLanguage` for extensible editor language detection
Expand Down
6 changes: 3 additions & 3 deletions TablePro/Core/Plugins/PluginManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ final class PluginManager {
/// Load all discovered but not-yet-loaded plugin bundles.
/// Safety fallback for code paths that need plugins before the deferred Task completes.
func loadPendingPlugins(clearRestartFlag: Bool = false) {
if clearRestartFlag {
_needsRestart = false
}
guard !pendingPluginURLs.isEmpty else { return }
let pending = pendingPluginURLs
pendingPluginURLs.removeAll()
Expand All @@ -124,9 +127,6 @@ final class PluginManager {
}

validateDependencies()
if clearRestartFlag {
_needsRestart = false
}
Self.logger.info("Loaded \(self.plugins.count) plugin(s): \(self.driverPlugins.count) driver(s), \(self.exportPlugins.count) export format(s), \(self.importPlugins.count) import format(s)")
}

Expand Down
38 changes: 4 additions & 34 deletions TablePro/Core/Plugins/Registry/DownloadCountService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,10 @@ final class DownloadCountService {
static let shared = DownloadCountService()

private var counts: [String: Int] = [:]
private var lastFetchDate: Date?
private static let cooldown: TimeInterval = 300 // 5 minutes
private static let logger = Logger(subsystem: "com.TablePro", category: "DownloadCountService")

private static let cacheKey = "downloadCountsCache"
private static let cacheDateKey = "downloadCountsCacheDate"
private static let cacheTTL: TimeInterval = 3_600 // 1 hour

// swiftlint:disable:next force_unwrapping
private static let releasesURL = URL(string: "https://api.github.com/repos/datlechin/TablePro/releases?per_page=100")!

Expand All @@ -27,8 +25,6 @@ final class DownloadCountService {
config.timeoutIntervalForRequest = 15
config.timeoutIntervalForResource = 30
self.session = URLSession(configuration: config)

loadCache()
}

// MARK: - Public
Expand All @@ -40,8 +36,7 @@ final class DownloadCountService {
func fetchCounts(for manifest: RegistryManifest?) async {
guard let manifest else { return }

if isCacheValid() {
Self.logger.debug("Using cached download counts")
if let lastFetchDate, Date().timeIntervalSince(lastFetchDate) < Self.cooldown {
return
}

Expand All @@ -60,7 +55,7 @@ final class DownloadCountService {
}

counts = totals
saveCache(totals)
lastFetchDate = Date()
Self.logger.info("Fetched download counts for \(totals.count) plugin(s)")
} catch {
Self.logger.error("Failed to fetch download counts: \(error.localizedDescription)")
Expand Down Expand Up @@ -102,31 +97,6 @@ final class DownloadCountService {
return map
}

// MARK: - Cache

private func isCacheValid() -> Bool {
guard let cacheDate = UserDefaults.standard.object(forKey: Self.cacheDateKey) as? Date else {
return false
}
return Date().timeIntervalSince(cacheDate) < Self.cacheTTL
}

private func loadCache() {
guard isCacheValid(),
let data = UserDefaults.standard.data(forKey: Self.cacheKey),
let cached = try? JSONDecoder().decode([String: Int].self, from: data) else {
counts = [:]
return
}
counts = cached
}

private func saveCache(_ totals: [String: Int]) {
if let data = try? JSONEncoder().encode(totals) {
UserDefaults.standard.set(data, forKey: Self.cacheKey)
UserDefaults.standard.set(Date(), forKey: Self.cacheDateKey)
}
}
}

// MARK: - GitHub API Models
Expand Down
Loading
Loading