Skip to content

Commit 1d2e86e

Browse files
authored
refactor(plugin-redis): replace recursive DisclosureGroup with OutlineGroup (#1132)
* refactor(datagrid): collapse cell hierarchy to single DataGridCellView * refactor(datagrid): bound display cache via NSCache and add O(1) RowID lookup * refactor(datagrid): incremental row visual state via RowVisualIndex * refactor(datagrid): off-main JSON parse and cancellable Task.sleep cooldowns * fix(datagrid): use NSButton for cell accessory clicks and brighten chevron contrast * refactor(datagrid): add typeSelect, animated undo insert, defensive row-heights flag * fix(datagrid): row tint refresh on mark-delete and focus-follow on programmatic selection * fix(datagrid): force focus overlay refresh on every selection change * fix(datagrid): keep focus overlay on top via zPosition and defer key-change handler * fix(datagrid): defer focus overlay refresh through every reload path * refactor(datagrid): cell-owned focus border replaces FocusOverlayView synchronization * chore(datagrid): delete dead code surfaced by audit * refactor(datagrid): snapshot theme palette per render pass and weak-cache connection data * refactor(quickswitcher): replace .sheet with NSPanel for Spotlight pattern * fix(switcher): ESC clears search if non-empty otherwise bubbles to dismiss * chore(datagrid): inline single-caller TypePicker and drop TableViewCoordinating protocol * refactor(window): replace custom restoration with NSWindowRestoration end-to-end * refactor(hig): honor Reduce Transparency and Increase Contrast for material backgrounds * refactor(datagrid): drop redundant resize cursor handling and consolidate sort dispatch * refactor(plugin-redis): replace recursive DisclosureGroup with OutlineGroup --------- Signed-off-by: Ngô Quốc Đạt <datlechin@gmail.com>
1 parent a61faa4 commit 1d2e86e

5 files changed

Lines changed: 42 additions & 63 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2626
- AI Chat views: replace custom pill buttons with native `.borderless` styles, switch hardcoded text colors to semantic system colors, use relative font sizing in Markdown rendering, align spacing to the 8-pt grid, and add accessibility labels to icon-only buttons
2727
- Translucent backgrounds (Welcome sidebar, settings banners, ER diagram toolbar, JSON editor controls, Pro feature scrim) honor the system Reduce Transparency and Increase Contrast accessibility settings, swapping the material for a solid surface color when either is on
2828
- Internal: result-grid sortable header drops the custom resize cursor handling that duplicated AppKit's built-in column-edge resize, and consolidates three sort delegate methods into one that carries the full sort state. No user-facing change; multi-column sort, shift-click cycle, and the column resize cursor still work the same.
29+
- Internal: Redis sidebar key tree uses SwiftUI `OutlineGroup` instead of recursive `DisclosureGroup` + `ForEach` wrapped in `AnyView`. Expansion state is now managed natively per branch identifier; the explicit `expandedPrefixes` set is gone.
2930

3031
### Fixed
3132

TablePro/Models/UI/RedisKeyNode.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,13 @@ internal enum RedisKeyNode: Identifiable, Hashable {
2323
}
2424
}
2525

26+
var children: [RedisKeyNode]? {
27+
switch self {
28+
case .namespace(_, _, let children, _): return children
29+
case .key: return nil
30+
}
31+
}
32+
2633
// Hash on id only (children excluded for performance)
2734
func hash(into hasher: inout Hasher) {
2835
hasher.combine(id)

TablePro/ViewModels/RedisKeyTreeViewModel.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ internal final class RedisKeyTreeViewModel {
1313
private static let maxKeys = 50_000
1414

1515
var rootNodes: [RedisKeyNode] = []
16-
var expandedPrefixes: Set<String> = []
1716
var isLoading = false
1817
var isTruncated = false
1918
var separator: String = ":"
@@ -65,7 +64,6 @@ internal final class RedisKeyTreeViewModel {
6564
func clear() {
6665
rootNodes = []
6766
allKeys = []
68-
expandedPrefixes = []
6967
isTruncated = false
7068
}
7169

TablePro/Views/Sidebar/RedisKeyTreeView.swift

Lines changed: 34 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import SwiftUI
77

88
internal struct RedisKeyTreeView: View {
99
let nodes: [RedisKeyNode]
10-
@Binding var expandedPrefixes: Set<String>
1110
let isLoading: Bool
1211
let isTruncated: Bool
1312
var onSelectNamespace: ((String) -> Void)?
@@ -29,7 +28,9 @@ internal struct RedisKeyTreeView: View {
2928
.font(.caption)
3029
.padding(.vertical, 4)
3130
} else {
32-
renderNodes(nodes)
31+
OutlineGroup(nodes, children: \.children) { node in
32+
row(for: node)
33+
}
3334
if isTruncated {
3435
Text("Showing first 50,000 keys")
3536
.foregroundStyle(.secondary)
@@ -39,65 +40,41 @@ internal struct RedisKeyTreeView: View {
3940
}
4041
}
4142

42-
private func renderNodes(_ items: [RedisKeyNode]) -> AnyView {
43-
AnyView(
44-
ForEach(items) { node in
45-
switch node {
46-
case .namespace(let name, let fullPrefix, let children, let keyCount):
47-
DisclosureGroup(isExpanded: Binding(
48-
get: { expandedPrefixes.contains(fullPrefix) },
49-
set: { expanded in
50-
if expanded {
51-
expandedPrefixes.insert(fullPrefix)
52-
} else {
53-
expandedPrefixes.remove(fullPrefix)
54-
}
55-
}
56-
)) {
57-
renderNodes(children)
58-
} label: {
59-
namespaceLabel(name: name, keyCount: keyCount, fullPrefix: fullPrefix)
60-
}
61-
case .key(let name, let fullKey, let keyType):
62-
keyLabel(name: name, fullKey: fullKey, keyType: keyType)
43+
@ViewBuilder
44+
private func row(for node: RedisKeyNode) -> some View {
45+
switch node {
46+
case .namespace(let name, let fullPrefix, _, let keyCount):
47+
Button {
48+
onSelectNamespace?(fullPrefix)
49+
} label: {
50+
HStack {
51+
Label(name, systemImage: "folder")
52+
.foregroundStyle(.primary)
53+
Spacer()
54+
Text("\(keyCount)")
55+
.font(.caption2)
56+
.foregroundStyle(.secondary)
57+
.padding(.horizontal, 6)
58+
.padding(.vertical, 1)
59+
.background(.quaternary, in: Capsule())
6360
}
6461
}
65-
)
66-
}
67-
68-
private func namespaceLabel(name: String, keyCount: Int, fullPrefix: String) -> some View {
69-
Button {
70-
onSelectNamespace?(fullPrefix)
71-
} label: {
72-
HStack {
73-
Label(name, systemImage: "folder")
74-
.foregroundStyle(.primary)
75-
Spacer()
76-
Text("\(keyCount)")
77-
.font(.caption2)
78-
.foregroundStyle(.secondary)
79-
.padding(.horizontal, 6)
80-
.padding(.vertical, 1)
81-
.background(.quaternary, in: Capsule())
82-
}
83-
}
84-
.buttonStyle(.plain)
85-
}
86-
87-
private func keyLabel(name: String, fullKey: String, keyType: String) -> some View {
88-
Button {
89-
onSelectKey?(fullKey, keyType)
90-
} label: {
91-
HStack {
92-
Label(name, systemImage: keyTypeIcon(keyType))
93-
.foregroundStyle(.primary)
94-
Spacer()
95-
Text(keyType)
96-
.font(.caption2)
97-
.foregroundStyle(.tertiary)
62+
.buttonStyle(.plain)
63+
case .key(let name, let fullKey, let keyType):
64+
Button {
65+
onSelectKey?(fullKey, keyType)
66+
} label: {
67+
HStack {
68+
Label(name, systemImage: keyTypeIcon(keyType))
69+
.foregroundStyle(.primary)
70+
Spacer()
71+
Text(keyType)
72+
.font(.caption2)
73+
.foregroundStyle(.tertiary)
74+
}
9875
}
76+
.buttonStyle(.plain)
9977
}
100-
.buttonStyle(.plain)
10178
}
10279

10380
private func keyTypeIcon(_ type: String) -> String {

TablePro/Views/Sidebar/SidebarView.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,6 @@ struct SidebarView: View {
222222
Section(isExpanded: $viewModel.isRedisKeysExpanded) {
223223
RedisKeyTreeView(
224224
nodes: keyTreeVM.displayNodes(searchText: viewModel.searchText),
225-
expandedPrefixes: Binding(
226-
get: { keyTreeVM.expandedPrefixes },
227-
set: { keyTreeVM.expandedPrefixes = $0 }
228-
),
229225
isLoading: keyTreeVM.isLoading,
230226
isTruncated: keyTreeVM.isTruncated,
231227
onSelectNamespace: { prefix in

0 commit comments

Comments
 (0)