Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

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

## [0.27.2] - 2026-04-02

Expand Down
12 changes: 12 additions & 0 deletions TablePro/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -23334,6 +23334,9 @@
},
"Preview Query" : {

},
"Preview Referenced Row" : {

},
"Preview Schema Changes" : {
"localizations" : {
Expand Down Expand Up @@ -27762,6 +27765,9 @@
}
}
}
},
"Show All Columns" : {

},
"Show All Databases" : {
"extractionState" : "stale",
Expand Down Expand Up @@ -28350,6 +28356,12 @@
}
}
}
},
"Sort Ascending" : {

},
"Sort Descending" : {

},
"Sorting will reload data and discard all unsaved changes." : {
"localizations" : {
Expand Down
17 changes: 17 additions & 0 deletions TablePro/Views/Main/Child/MainEditorContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,23 @@ struct MainEditorContentView: View {
onHideColumn: { [coordinator] columnName in
coordinator.hideColumn(columnName)
},
onShowAllColumns: { [columnVisibilityManager, coordinator] in
columnVisibilityManager.showAll()
coordinator.saveColumnVisibilityToTab()
},
emptySpaceMenu: (tab.isEditable && !tab.isView && tab.tableName != nil) ? { [onAddRow] in
let menu = NSMenu()
let target = StructureMenuTarget { onAddRow() }
let item = NSMenuItem(
title: String(localized: "Add Row"),
action: #selector(StructureMenuTarget.addNewItem),
keyEquivalent: ""
)
item.target = target
item.representedObject = target
menu.addItem(item)
return menu
} : nil,
selectedRowIndices: $selectedRowIndices,
sortState: sortStateBinding(for: tab),
editingCell: $editingCell,
Expand Down
2 changes: 2 additions & 0 deletions TablePro/Views/Results/DataGridCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
var onUndoInsert: ((Int) -> Void)?
var onFilterColumn: ((String) -> Void)?
var onHideColumn: ((String) -> Void)?
var onShowAllColumns: (() -> Void)?
var onMoveRow: ((Int, Int) -> Void)?
var rowViewProvider: ((NSTableView, Int, TableViewCoordinator) -> NSTableRowView)?
var emptySpaceMenu: (() -> NSMenu?)?
Expand Down Expand Up @@ -244,6 +245,7 @@ final class TableViewCoordinator: NSObject, NSTableViewDelegate, NSTableViewData
onUndoInsert = nil
onFilterColumn = nil
onHideColumn = nil
onShowAllColumns = nil
onNavigateFK = nil
rowViewProvider = nil
emptySpaceMenu = nil
Expand Down
4 changes: 4 additions & 0 deletions TablePro/Views/Results/DataGridView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ struct DataGridView: NSViewRepresentable {
var showRowNumbers: Bool = true
var hiddenColumns: Set<String> = []
var onHideColumn: ((String) -> Void)?
var onShowAllColumns: (() -> Void)?
var onMoveRow: ((Int, Int) -> Void)?
var rowViewProvider: ((NSTableView, Int, TableViewCoordinator) -> NSTableRowView)?
var emptySpaceMenu: (() -> NSMenu?)?
Expand Down Expand Up @@ -181,6 +182,7 @@ struct DataGridView: NSViewRepresentable {
context.coordinator.onMoveRow = onMoveRow
context.coordinator.rowViewProvider = rowViewProvider
context.coordinator.emptySpaceMenu = emptySpaceMenu
context.coordinator.onShowAllColumns = onShowAllColumns
context.coordinator.rebuildColumnMetadataCache()
if let connectionId {
context.coordinator.observeTeardown(connectionId: connectionId)
Expand Down Expand Up @@ -239,6 +241,7 @@ struct DataGridView: NSViewRepresentable {
coordinator.onUndoInsert = onUndoInsert
coordinator.onFilterColumn = onFilterColumn
coordinator.onHideColumn = onHideColumn
coordinator.onShowAllColumns = onShowAllColumns
coordinator.onMoveRow = onMoveRow
coordinator.onRefresh = onRefresh
coordinator.onDeleteRows = onDeleteRows
Expand Down Expand Up @@ -310,6 +313,7 @@ struct DataGridView: NSViewRepresentable {
coordinator.onUndoInsert = onUndoInsert
coordinator.onFilterColumn = onFilterColumn
coordinator.onHideColumn = onHideColumn
coordinator.onShowAllColumns = onShowAllColumns
coordinator.onMoveRow = onMoveRow
coordinator.getVisualState = getVisualState
coordinator.onNavigateFK = onNavigateFK
Expand Down
47 changes: 47 additions & 0 deletions TablePro/Views/Results/Extensions/DataGridView+Sort.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ extension TableViewCoordinator {
return column.title
}()

if let dataColumnIndex = DataGridView.columnIndex(from: column.identifier) {
let sortAscItem = NSMenuItem(
title: String(localized: "Sort Ascending"),
action: #selector(sortAscending(_:)),
keyEquivalent: ""
)
sortAscItem.representedObject = dataColumnIndex
sortAscItem.target = self
menu.addItem(sortAscItem)

let sortDescItem = NSMenuItem(
title: String(localized: "Sort Descending"),
action: #selector(sortDescending(_:)),
keyEquivalent: ""
)
sortDescItem.representedObject = dataColumnIndex
sortDescItem.target = self
menu.addItem(sortDescItem)

menu.addItem(NSMenuItem.separator())
}

let copyItem = NSMenuItem(title: String(localized: "Copy Column Name"), action: #selector(copyColumnName(_:)), keyEquivalent: "")
copyItem.representedObject = baseName
copyItem.target = self
Expand Down Expand Up @@ -98,6 +120,31 @@ extension TableViewCoordinator {
hideItem.representedObject = baseName
hideItem.target = self
menu.addItem(hideItem)

if onShowAllColumns != nil,
tableView.tableColumns.contains(where: { $0.isHidden && $0.identifier.rawValue != "__rowNumber__" }) {
let showAllItem = NSMenuItem(
title: String(localized: "Show All Columns"),
action: #selector(showAllColumns),
keyEquivalent: ""
)
showAllItem.target = self
menu.addItem(showAllItem)
}
}

@objc func sortAscending(_ sender: NSMenuItem) {
guard let columnIndex = sender.representedObject as? Int else { return }
onSort?(columnIndex, true, false)
}

@objc func sortDescending(_ sender: NSMenuItem) {
guard let columnIndex = sender.representedObject as? Int else { return }
onSort?(columnIndex, false, false)
}

@objc func showAllColumns() {
onShowAllColumns?()
}

@objc func copyColumnName(_ sender: NSMenuItem) {
Expand Down
45 changes: 45 additions & 0 deletions TablePro/Views/Results/TableRowViewWithMenu.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,34 @@ final class TableRowViewWithMenu: NSTableRowView {
menu.addItem(pasteItem)
}

// FK actions (only for FK columns with non-empty values)
if dataColumnIndex >= 0, dataColumnIndex < coordinator.rowProvider.columns.count {
let columnName = coordinator.rowProvider.columns[dataColumnIndex]
if let fkInfo = coordinator.rowProvider.columnForeignKeys[columnName],
let cellValue = coordinator.rowProvider.value(atRow: rowIndex, column: dataColumnIndex),
!cellValue.isEmpty {
menu.addItem(NSMenuItem.separator())

let previewItem = NSMenuItem(
title: String(localized: "Preview Referenced Row"),
action: #selector(previewForeignKey(_:)),
keyEquivalent: ""
)
previewItem.representedObject = dataColumnIndex
previewItem.target = self
menu.addItem(previewItem)

let navItem = NSMenuItem(
title: String(format: String(localized: "Open %@"), fkInfo.referencedTable),
action: #selector(navigateToForeignKey(_:)),
keyEquivalent: ""
)
navItem.representedObject = dataColumnIndex
navItem.target = self
menu.addItem(navItem)
}
}

if coordinator.isEditable {
menu.addItem(NSMenuItem.separator())
}
Expand Down Expand Up @@ -270,4 +298,21 @@ final class TableRowViewWithMenu: NSTableRowView {
: [rowIndex]
coordinator.copyRowsAsJson(at: indices)
}

@objc private func previewForeignKey(_ sender: NSMenuItem) {
guard let columnIndex = sender.representedObject as? Int,
let coordinator, let tableView = coordinator.tableView else { return }
coordinator.showForeignKeyPreview(
tableView: tableView, row: rowIndex, column: columnIndex + 1, columnIndex: columnIndex
)
}

@objc private func navigateToForeignKey(_ sender: NSMenuItem) {
guard let columnIndex = sender.representedObject as? Int,
let coordinator else { return }
let columnName = coordinator.rowProvider.columns[columnIndex]
guard let fkInfo = coordinator.rowProvider.columnForeignKeys[columnName],
let value = coordinator.rowProvider.value(atRow: rowIndex, column: columnIndex) else { return }
coordinator.onNavigateFK?(value, fkInfo)
}
}
Loading