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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Oracle connections can now use a SID instead of a service name. Set Connection Type to SID in the connection form and enter the SID. (#1425)
- Cmd-click a foreign key arrow to open the referenced table in a new tab instead of the current one. The right-click menu has the same Open in New Tab option. (#1421)
- Cells holding JSON or PHP serialized values in text columns now open in the structured viewer automatically, without requiring the column type to be JSON.
- Add and remove buttons in the table structure editor for columns, indexes, and foreign keys, on the bottom status bar alongside the view-mode picker. Cmd+Shift+N adds and Cmd+Delete removes. An empty Indexes or Foreign Keys tab also shows a labelled add button. (#1319)

### Changed

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

},
"Remove Column" : {

},
"Remove Failed" : {

Expand Down Expand Up @@ -38455,6 +38458,9 @@
}
}
}
},
"Remove Foreign Key" : {

},
"Remove from Group" : {
"localizations" : {
Expand Down Expand Up @@ -38483,6 +38489,9 @@
},
"Remove image" : {

},
"Remove Index" : {

},
"Remove jump host" : {
"localizations" : {
Expand Down
5 changes: 4 additions & 1 deletion TablePro/Views/Main/Child/MainEditorContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,10 @@ struct MainEditorContentView: View {
onShowAllColumns: { coordinator.showAllColumns() },
onHideAllColumns: { coordinator.hideAllColumns($0) },
onToggleFilters: { coordinator.toggleFilterPanel() },
onFetchAll: { coordinator.fetchAllRows() }
onFetchAll: { coordinator.fetchAllRows() },
structureFooterState: coordinator.structureFooterState,
onStructureAdd: { coordinator.structureActions?.addRow?() },
onStructureRemove: { coordinator.structureActions?.removeRow?() }
)
}

Expand Down
154 changes: 96 additions & 58 deletions TablePro/Views/Main/Child/MainStatusBarView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,17 @@ struct MainStatusBarView: View {
// Truncated result callback
var onFetchAll: (() -> Void)?

// Structure-mode footer accessory (Add/Remove buttons)
let structureFooterState: StructureFooterState?
let onStructureAdd: (() -> Void)?
let onStructureRemove: (() -> Void)?

private var hasHiddenColumns: Bool { !hiddenColumns.isEmpty }
private var hiddenCount: Int { hiddenColumns.count }

private var isStructureMode: Bool { viewMode == .structure }
private var showsDataChrome: Bool { !isStructureMode }

var body: some View {
HStack {
if snapshot.tabId != nil {
Expand Down Expand Up @@ -89,8 +97,7 @@ struct MainStatusBarView: View {

Spacer()

// Center: Row info (selection or pagination summary) and status message
if snapshot.hasRows {
if showsDataChrome, snapshot.hasRows {
HStack(spacing: 4) {
if snapshot.pagination.isLoadingMore {
ProgressView()
Expand Down Expand Up @@ -132,71 +139,74 @@ struct MainStatusBarView: View {

Spacer()

// Right: Columns, Filters toggle and Pagination controls
HStack(spacing: 8) {
// Columns visibility button (works for both table and query tabs)
if snapshot.hasColumns {
Button {
showColumnPopover.toggle()
} label: {
HStack(spacing: 4) {
Image(systemName: hasHiddenColumns
? "eye.slash.circle.fill"
: "eye.circle")
Text("Columns")
if hasHiddenColumns {
let visible = allColumns.count - hiddenCount
Text("(\(visible)/\(allColumns.count))")
.foregroundStyle(.secondary)
if isStructureMode, let state = structureFooterState, state.isActive {
structureFooterControls(state: state)
}

if showsDataChrome {
if snapshot.hasColumns {
Button {
showColumnPopover.toggle()
} label: {
HStack(spacing: 4) {
Image(systemName: hasHiddenColumns
? "eye.slash.circle.fill"
: "eye.circle")
Text("Columns")
if hasHiddenColumns {
let visible = allColumns.count - hiddenCount
Text("(\(visible)/\(allColumns.count))")
.foregroundStyle(.secondary)
}
}
}
.controlSize(.small)
.popover(isPresented: $showColumnPopover) {
ColumnVisibilityPopover(
columns: allColumns,
hiddenColumns: hiddenColumns,
onToggleColumn: onToggleColumn,
onShowAll: onShowAllColumns,
onHideAll: onHideAllColumns
)
}
}
.controlSize(.small)
.popover(isPresented: $showColumnPopover) {
ColumnVisibilityPopover(
columns: allColumns,
hiddenColumns: hiddenColumns,
onToggleColumn: onToggleColumn,
onShowAll: onShowAllColumns,
onHideAll: onHideAllColumns
)
}
}

// Filters toggle button
if snapshot.tabType == .table, snapshot.hasTableName {
Toggle(isOn: Binding(
get: { filterState.isVisible },
set: { _ in onToggleFilters() }
)) {
HStack(spacing: 4) {
Image(systemName: filterState.hasAppliedFilters
? "line.3.horizontal.decrease.circle.fill"
: "line.3.horizontal.decrease.circle")
Text("Filters")
if filterState.hasAppliedFilters {
Text("(\(filterState.appliedFilters.count))")
.foregroundStyle(.secondary)
if snapshot.tabType == .table, snapshot.hasTableName {
Toggle(isOn: Binding(
get: { filterState.isVisible },
set: { _ in onToggleFilters() }
)) {
HStack(spacing: 4) {
Image(systemName: filterState.hasAppliedFilters
? "line.3.horizontal.decrease.circle.fill"
: "line.3.horizontal.decrease.circle")
Text("Filters")
if filterState.hasAppliedFilters {
Text("(\(filterState.appliedFilters.count))")
.foregroundStyle(.secondary)
}
}
}
.toggleStyle(.button)
.controlSize(.small)
.help(String(localized: "Toggle Filters (⇧⌘F)"))
}
.toggleStyle(.button)
.controlSize(.small)
.help(String(localized: "Toggle Filters (⇧⌘F)"))
}

if snapshot.tabType == .table, snapshot.hasTableName, showsPaginationControls {
PaginationControlsView(
pagination: snapshot.pagination,
loadedRowCount: snapshot.rowCount,
onFirst: onFirstPage,
onPrevious: onPreviousPage,
onNext: onNextPage,
onLast: onLastPage,
onPageSizeChange: onPageSizeChange,
onShowAll: onShowAll,
onGoToPage: onGoToPage
)
if snapshot.tabType == .table, snapshot.hasTableName, showsPaginationControls {
PaginationControlsView(
pagination: snapshot.pagination,
loadedRowCount: snapshot.rowCount,
onFirst: onFirstPage,
onPrevious: onPreviousPage,
onNext: onNextPage,
onLast: onLastPage,
onPageSizeChange: onPageSizeChange,
onShowAll: onShowAll,
onGoToPage: onGoToPage
)
}
}
}
}
Expand All @@ -208,6 +218,34 @@ struct MainStatusBarView: View {
}
}

@ViewBuilder
private func structureFooterControls(state: StructureFooterState) -> some View {
ControlGroup {
Button {
onStructureAdd?()
} label: {
Label(state.addLabel, systemImage: "plus")
.labelStyle(.iconOnly)
}
.help(state.addLabel)
.accessibilityLabel(state.addLabel)
.disabled(!state.canAdd)

Button {
onStructureRemove?()
} label: {
Label(state.removeLabel, systemImage: "minus")
.labelStyle(.iconOnly)
}
.help(state.removeLabel)
.accessibilityLabel(state.removeLabel)
.disabled(!state.canRemove)
}
.controlGroupStyle(.navigation)
.controlSize(.small)
.fixedSize()
}

private var showsPaginationControls: Bool {
if let total = snapshot.pagination.totalRowCount, total > 0 { return true }
return isPagedWithUnknownTotal
Expand Down
5 changes: 5 additions & 0 deletions TablePro/Views/Main/MainContentCommandActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ final class MainContentCommandActions {
func deleteSelectedRows(rowIndices: Set<Int>? = nil) {
let fromDataGrid = rowIndices != nil

if coordinator?.tabManager.selectedTab?.display.resultsViewMode == .structure {
coordinator?.structureActions?.removeRow?()
return
}

let indices = rowIndices ?? selectionState.indices
if !indices.isEmpty {
coordinator?.deleteSelectedRows(indices: indices)
Expand Down
4 changes: 4 additions & 0 deletions TablePro/Views/Main/MainContentCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ final class MainContentCoordinator {
/// Direct reference to structure view actions — eliminates notification broadcasts
weak var structureActions: StructureViewActionHandler?

/// Published capability/labels for the structure-mode footer in the bottom status bar.
/// `TableStructureView` writes to this; `MainStatusBarView` reads from it.
let structureFooterState = StructureFooterState()

/// Direct reference to AI chat viewmodel — eliminates notification broadcasts
weak var aiViewModel: AIChatViewModel?

Expand Down
44 changes: 44 additions & 0 deletions TablePro/Views/Structure/StructureFooterState.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// StructureFooterState.swift
// TablePro
//

import Foundation
import Observation

@Observable
@MainActor
final class StructureFooterState {
var isActive: Bool = false
var canAdd: Bool = false
var canRemove: Bool = false
var addLabel: String = ""
var removeLabel: String = ""

private(set) var currentOwner: UUID?

func update(
owner: UUID,
canAdd: Bool,
canRemove: Bool,
addLabel: String,
removeLabel: String
) {
currentOwner = owner
isActive = true
self.canAdd = canAdd
self.canRemove = canRemove
self.addLabel = addLabel
self.removeLabel = removeLabel
}

func deactivate(owner: UUID) {
guard currentOwner == owner else { return }
currentOwner = nil
isActive = false
canAdd = false
canRemove = false
addLabel = ""
removeLabel = ""
}
}
6 changes: 1 addition & 5 deletions TablePro/Views/Structure/StructureViewActionHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,9 @@
// StructureViewActionHandler.swift
// TablePro
//
// Action handler for structure view — allows coordinator to call
// structure-view actions directly instead of broadcasting notifications.
//

import Foundation

/// Provides direct action dispatch from coordinator to structure view,
/// replacing notification-based communication.
@MainActor
final class StructureViewActionHandler {
var saveChanges: (() -> Void)?
Expand All @@ -19,4 +14,5 @@ final class StructureViewActionHandler {
var undo: (() -> Void)?
var redo: (() -> Void)?
var addRow: (() -> Void)?
var removeRow: (() -> Void)?
}
Loading
Loading