Skip to content

Commit 8849592

Browse files
authored
feat: context menu improvements for header, FK cells, and empty space (#563)
1 parent 8d27b7b commit 8849592

7 files changed

Lines changed: 130 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Structure tab context menu with Copy Name, Copy Definition (SQL), Duplicate, and Delete for columns, indexes, and foreign keys
1313
- Foreign key preview: press Cmd+Enter on a FK cell to see the referenced row in a popover
14+
- Column header: sort ascending/descending and show all hidden columns in context menu
15+
- Data grid: preview and navigate FK references from right-click context menu
16+
- Data grid: add row from right-click on empty space
1417

1518
## [0.27.2] - 2026-04-02
1619

TablePro/Resources/Localizable.xcstrings

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23334,6 +23334,9 @@
2333423334
},
2333523335
"Preview Query" : {
2333623336

23337+
},
23338+
"Preview Referenced Row" : {
23339+
2333723340
},
2333823341
"Preview Schema Changes" : {
2333923342
"localizations" : {
@@ -27762,6 +27765,9 @@
2776227765
}
2776327766
}
2776427767
}
27768+
},
27769+
"Show All Columns" : {
27770+
2776527771
},
2776627772
"Show All Databases" : {
2776727773
"extractionState" : "stale",
@@ -28350,6 +28356,12 @@
2835028356
}
2835128357
}
2835228358
}
28359+
},
28360+
"Sort Ascending" : {
28361+
28362+
},
28363+
"Sort Descending" : {
28364+
2835328365
},
2835428366
"Sorting will reload data and discard all unsaved changes." : {
2835528367
"localizations" : {

TablePro/Views/Main/Child/MainEditorContentView.swift

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,23 @@ struct MainEditorContentView: View {
446446
onHideColumn: { [coordinator] columnName in
447447
coordinator.hideColumn(columnName)
448448
},
449+
onShowAllColumns: { [columnVisibilityManager, coordinator] in
450+
columnVisibilityManager.showAll()
451+
coordinator.saveColumnVisibilityToTab()
452+
},
453+
emptySpaceMenu: (tab.isEditable && !tab.isView && tab.tableName != nil) ? { [onAddRow] in
454+
let menu = NSMenu()
455+
let target = StructureMenuTarget { onAddRow() }
456+
let item = NSMenuItem(
457+
title: String(localized: "Add Row"),
458+
action: #selector(StructureMenuTarget.addNewItem),
459+
keyEquivalent: ""
460+
)
461+
item.target = target
462+
item.representedObject = target
463+
menu.addItem(item)
464+
return menu
465+
} : nil,
449466
selectedRowIndices: $selectedRowIndices,
450467
sortState: sortStateBinding(for: tab),
451468
editingCell: $editingCell,

TablePro/Views/Results/DataGridCoordinator.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
3030
var onUndoInsert: ((Int) -> Void)?
3131
var onFilterColumn: ((String) -> Void)?
3232
var onHideColumn: ((String) -> Void)?
33+
var onShowAllColumns: (() -> Void)?
3334
var onMoveRow: ((Int, Int) -> Void)?
3435
var rowViewProvider: ((NSTableView, Int, TableViewCoordinator) -> NSTableRowView)?
3536
var emptySpaceMenu: (() -> NSMenu?)?
@@ -244,6 +245,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
244245
onUndoInsert = nil
245246
onFilterColumn = nil
246247
onHideColumn = nil
248+
onShowAllColumns = nil
247249
onNavigateFK = nil
248250
rowViewProvider = nil
249251
emptySpaceMenu = nil

TablePro/Views/Results/DataGridView.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct DataGridView: NSViewRepresentable {
6565
var showRowNumbers: Bool = true
6666
var hiddenColumns: Set<String> = []
6767
var onHideColumn: ((String) -> Void)?
68+
var onShowAllColumns: (() -> Void)?
6869
var onMoveRow: ((Int, Int) -> Void)?
6970
var rowViewProvider: ((NSTableView, Int, TableViewCoordinator) -> NSTableRowView)?
7071
var emptySpaceMenu: (() -> NSMenu?)?
@@ -181,6 +182,7 @@ struct DataGridView: NSViewRepresentable {
181182
context.coordinator.onMoveRow = onMoveRow
182183
context.coordinator.rowViewProvider = rowViewProvider
183184
context.coordinator.emptySpaceMenu = emptySpaceMenu
185+
context.coordinator.onShowAllColumns = onShowAllColumns
184186
context.coordinator.rebuildColumnMetadataCache()
185187
if let connectionId {
186188
context.coordinator.observeTeardown(connectionId: connectionId)
@@ -239,6 +241,7 @@ struct DataGridView: NSViewRepresentable {
239241
coordinator.onUndoInsert = onUndoInsert
240242
coordinator.onFilterColumn = onFilterColumn
241243
coordinator.onHideColumn = onHideColumn
244+
coordinator.onShowAllColumns = onShowAllColumns
242245
coordinator.onMoveRow = onMoveRow
243246
coordinator.onRefresh = onRefresh
244247
coordinator.onDeleteRows = onDeleteRows
@@ -310,6 +313,7 @@ struct DataGridView: NSViewRepresentable {
310313
coordinator.onUndoInsert = onUndoInsert
311314
coordinator.onFilterColumn = onFilterColumn
312315
coordinator.onHideColumn = onHideColumn
316+
coordinator.onShowAllColumns = onShowAllColumns
313317
coordinator.onMoveRow = onMoveRow
314318
coordinator.getVisualState = getVisualState
315319
coordinator.onNavigateFK = onNavigateFK

TablePro/Views/Results/Extensions/DataGridView+Sort.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,28 @@ extension TableViewCoordinator {
7171
return column.title
7272
}()
7373

74+
if let dataColumnIndex = DataGridView.columnIndex(from: column.identifier) {
75+
let sortAscItem = NSMenuItem(
76+
title: String(localized: "Sort Ascending"),
77+
action: #selector(sortAscending(_:)),
78+
keyEquivalent: ""
79+
)
80+
sortAscItem.representedObject = dataColumnIndex
81+
sortAscItem.target = self
82+
menu.addItem(sortAscItem)
83+
84+
let sortDescItem = NSMenuItem(
85+
title: String(localized: "Sort Descending"),
86+
action: #selector(sortDescending(_:)),
87+
keyEquivalent: ""
88+
)
89+
sortDescItem.representedObject = dataColumnIndex
90+
sortDescItem.target = self
91+
menu.addItem(sortDescItem)
92+
93+
menu.addItem(NSMenuItem.separator())
94+
}
95+
7496
let copyItem = NSMenuItem(title: String(localized: "Copy Column Name"), action: #selector(copyColumnName(_:)), keyEquivalent: "")
7597
copyItem.representedObject = baseName
7698
copyItem.target = self
@@ -98,6 +120,31 @@ extension TableViewCoordinator {
98120
hideItem.representedObject = baseName
99121
hideItem.target = self
100122
menu.addItem(hideItem)
123+
124+
if onShowAllColumns != nil,
125+
tableView.tableColumns.contains(where: { $0.isHidden && $0.identifier.rawValue != "__rowNumber__" }) {
126+
let showAllItem = NSMenuItem(
127+
title: String(localized: "Show All Columns"),
128+
action: #selector(showAllColumns),
129+
keyEquivalent: ""
130+
)
131+
showAllItem.target = self
132+
menu.addItem(showAllItem)
133+
}
134+
}
135+
136+
@objc func sortAscending(_ sender: NSMenuItem) {
137+
guard let columnIndex = sender.representedObject as? Int else { return }
138+
onSort?(columnIndex, true, false)
139+
}
140+
141+
@objc func sortDescending(_ sender: NSMenuItem) {
142+
guard let columnIndex = sender.representedObject as? Int else { return }
143+
onSort?(columnIndex, false, false)
144+
}
145+
146+
@objc func showAllColumns() {
147+
onShowAllColumns?()
101148
}
102149

103150
@objc func copyColumnName(_ sender: NSMenuItem) {

TablePro/Views/Results/TableRowViewWithMenu.swift

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,34 @@ final class TableRowViewWithMenu: NSTableRowView {
101101
menu.addItem(pasteItem)
102102
}
103103

104+
// FK actions (only for FK columns with non-empty values)
105+
if dataColumnIndex >= 0, dataColumnIndex < coordinator.rowProvider.columns.count {
106+
let columnName = coordinator.rowProvider.columns[dataColumnIndex]
107+
if let fkInfo = coordinator.rowProvider.columnForeignKeys[columnName],
108+
let cellValue = coordinator.rowProvider.value(atRow: rowIndex, column: dataColumnIndex),
109+
!cellValue.isEmpty {
110+
menu.addItem(NSMenuItem.separator())
111+
112+
let previewItem = NSMenuItem(
113+
title: String(localized: "Preview Referenced Row"),
114+
action: #selector(previewForeignKey(_:)),
115+
keyEquivalent: ""
116+
)
117+
previewItem.representedObject = dataColumnIndex
118+
previewItem.target = self
119+
menu.addItem(previewItem)
120+
121+
let navItem = NSMenuItem(
122+
title: String(format: String(localized: "Open %@"), fkInfo.referencedTable),
123+
action: #selector(navigateToForeignKey(_:)),
124+
keyEquivalent: ""
125+
)
126+
navItem.representedObject = dataColumnIndex
127+
navItem.target = self
128+
menu.addItem(navItem)
129+
}
130+
}
131+
104132
if coordinator.isEditable {
105133
menu.addItem(NSMenuItem.separator())
106134
}
@@ -270,4 +298,21 @@ final class TableRowViewWithMenu: NSTableRowView {
270298
: [rowIndex]
271299
coordinator.copyRowsAsJson(at: indices)
272300
}
301+
302+
@objc private func previewForeignKey(_ sender: NSMenuItem) {
303+
guard let columnIndex = sender.representedObject as? Int,
304+
let coordinator, let tableView = coordinator.tableView else { return }
305+
coordinator.showForeignKeyPreview(
306+
tableView: tableView, row: rowIndex, column: columnIndex + 1, columnIndex: columnIndex
307+
)
308+
}
309+
310+
@objc private func navigateToForeignKey(_ sender: NSMenuItem) {
311+
guard let columnIndex = sender.representedObject as? Int,
312+
let coordinator else { return }
313+
let columnName = coordinator.rowProvider.columns[columnIndex]
314+
guard let fkInfo = coordinator.rowProvider.columnForeignKeys[columnName],
315+
let value = coordinator.rowProvider.value(atRow: rowIndex, column: columnIndex) else { return }
316+
coordinator.onNavigateFK?(value, fkInfo)
317+
}
273318
}

0 commit comments

Comments
 (0)