Skip to content

Commit aaa85b0

Browse files
authored
feat: polish plugin settings UI with enriched rows and status badges (#316)
* feat: redesign Plugins settings with HSplitView master-detail layout * docs: add CHANGELOG entry and update plugin settings docs for HSplitView layout * fix: address PR review feedback from CodeRabbit * fix: clear needsRestart flag before early return guard in loadPendingPlugins * refactor: replace HSplitView with NavigationSplitView for modern macOS feel * fix: revert NavigationSplitView to HSplitView to fix settings layout * refactor: move enable/disable toggle from list rows to detail pane * refactor: remove compact install button from browse list rows * feat: add search filter to installed plugins list * fix: show loading/empty/error states full-width in browse tab * fix: make empty/loading states fill available space in browse tab * refactor: polish plugin settings UI layout and consistency * refactor: remove dead RegistryPluginRow and update docs for toggle location * feat: enrich plugin list rows with version, status badges, and download counts * docs: update plugin settings docs for enriched rows (en, vi, zh) * fix: address PR review — extract PluginIconView, fix disabled condition, add accessibility and translations
1 parent 67b33f8 commit aaa85b0

File tree

12 files changed

+11568
-11651
lines changed

12 files changed

+11568
-11651
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12-
- Redesigned Plugins settings tab with HSplitView master-detail layout: plugin list on the left, detail pane on the right, matching macOS conventions
12+
- 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
13+
- Download counts in browse tab now always fetch latest from GitHub API (5-minute in-memory cooldown per session)
1314
- 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
1415
- 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
1516
- Replaced `AppState.isMongoDB`/`isRedis` booleans with `AppState.editorLanguage: EditorLanguage` for extensible editor language detection

TablePro/Core/Plugins/PluginManager.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ final class PluginManager {
111111
/// Load all discovered but not-yet-loaded plugin bundles.
112112
/// Safety fallback for code paths that need plugins before the deferred Task completes.
113113
func loadPendingPlugins(clearRestartFlag: Bool = false) {
114+
if clearRestartFlag {
115+
_needsRestart = false
116+
}
114117
guard !pendingPluginURLs.isEmpty else { return }
115118
let pending = pendingPluginURLs
116119
pendingPluginURLs.removeAll()
@@ -124,9 +127,6 @@ final class PluginManager {
124127
}
125128

126129
validateDependencies()
127-
if clearRestartFlag {
128-
_needsRestart = false
129-
}
130130
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)")
131131
}
132132

TablePro/Core/Plugins/Registry/DownloadCountService.swift

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,10 @@ final class DownloadCountService {
1111
static let shared = DownloadCountService()
1212

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

16-
private static let cacheKey = "downloadCountsCache"
17-
private static let cacheDateKey = "downloadCountsCacheDate"
18-
private static let cacheTTL: TimeInterval = 3_600 // 1 hour
19-
2018
// swiftlint:disable:next force_unwrapping
2119
private static let releasesURL = URL(string: "https://api.github.com/repos/datlechin/TablePro/releases?per_page=100")!
2220

@@ -27,8 +25,6 @@ final class DownloadCountService {
2725
config.timeoutIntervalForRequest = 15
2826
config.timeoutIntervalForResource = 30
2927
self.session = URLSession(configuration: config)
30-
31-
loadCache()
3228
}
3329

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

43-
if isCacheValid() {
44-
Self.logger.debug("Using cached download counts")
39+
if let lastFetchDate, Date().timeIntervalSince(lastFetchDate) < Self.cooldown {
4540
return
4641
}
4742

@@ -60,7 +55,7 @@ final class DownloadCountService {
6055
}
6156

6257
counts = totals
63-
saveCache(totals)
58+
lastFetchDate = Date()
6459
Self.logger.info("Fetched download counts for \(totals.count) plugin(s)")
6560
} catch {
6661
Self.logger.error("Failed to fetch download counts: \(error.localizedDescription)")
@@ -102,31 +97,6 @@ final class DownloadCountService {
10297
return map
10398
}
10499

105-
// MARK: - Cache
106-
107-
private func isCacheValid() -> Bool {
108-
guard let cacheDate = UserDefaults.standard.object(forKey: Self.cacheDateKey) as? Date else {
109-
return false
110-
}
111-
return Date().timeIntervalSince(cacheDate) < Self.cacheTTL
112-
}
113-
114-
private func loadCache() {
115-
guard isCacheValid(),
116-
let data = UserDefaults.standard.data(forKey: Self.cacheKey),
117-
let cached = try? JSONDecoder().decode([String: Int].self, from: data) else {
118-
counts = [:]
119-
return
120-
}
121-
counts = cached
122-
}
123-
124-
private func saveCache(_ totals: [String: Int]) {
125-
if let data = try? JSONEncoder().encode(totals) {
126-
UserDefaults.standard.set(data, forKey: Self.cacheKey)
127-
UserDefaults.standard.set(Date(), forKey: Self.cacheDateKey)
128-
}
129-
}
130100
}
131101

132102
// MARK: - GitHub API Models

0 commit comments

Comments
 (0)