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
2 changes: 0 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Connection rows without colored dots, on the Mac and on iOS.
- SQL Server sessions open with the ANSI SET profile the server requires, matching every other client.
- Compared columns in data Compare & Sync chosen per table, and saved with each table's key, filter and row limit. (#2537)
- Query history opens in the trailing pane beside the inspector and the assistant, with its entry list above its detail.
- Query editor command bar with one control size, the container picker leading and the commands trailing.

### Removed
Expand All @@ -64,7 +63,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `Ctrl+Cmd+J` from the editor's reserved shortcuts, so it can be bound in Settings > Keyboard.
- Result tab strip above the query results, and the "Query" heading above the editor.
- Trash button that cleared the query and the results under one name.
- Query history drawer under the editor and results.

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,10 +333,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
super.splitViewDidResizeSubviews(notification)
recomputeWindowMinSize()
toolbarOwner?.syncSidebarSelection()
/// A divider drag can collapse the trailing pane without going through `hideTrailingPane`,
/// and history's activation is keyed on the flag rather than on the pane, so it has to be
/// reconciled here as well or the panel keeps querying behind a collapsed divider.
syncHistoryPanelVisibility()
}

override func viewWillAppear() {
Expand Down Expand Up @@ -721,7 +717,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
workspace.panes.detail.rootView = AnyView(buildDetailView(for: workspace))
workspace.panes.inspector.rootView = AnyView(buildInspectorView(for: workspace))
workspace.panes.assistant.rootView = AnyView(buildAssistantView(for: workspace))
workspace.panes.history.rootView = AnyView(buildHistoryView(for: workspace))
refreshTabStripPane(of: workspace)
workspace.panes.markRendered(workspace.paneRenderKey)
guard isShowing(workspace) else { return }
Expand Down Expand Up @@ -902,16 +897,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
}
}

@ViewBuilder
private func buildHistoryView(for workspace: ConnectionWorkspace) -> some View {
if workspace.resolvedPane == .content,
let coordinator = workspace.sessionState?.coordinator {
HistoryPanelView(coordinator: coordinator)
} else {
TrailingPaneUnavailableView(surface: .history)
}
}

/// Rebuilds the trailing surfaces alone. `commandActions` is read eagerly by both, and it only
/// exists once the detail pane has appeared, which is after `rebuildPanes()` has already built
/// them against a nil value. Rebuilding the detail pane too would remount the very view that
Expand All @@ -920,7 +905,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
guard let selected = workspaces.selected else { return }
selected.panes.inspector.rootView = AnyView(buildInspectorView(for: selected))
selected.panes.assistant.rootView = AnyView(buildAssistantView(for: selected))
selected.panes.history.rootView = AnyView(buildHistoryView(for: selected))
}

/// Parents whichever surface the selected workspace is showing.
Expand All @@ -939,11 +923,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
isAIEnabled: AppSettingsManager.shared.ai.enabled
)
inspectorPaneHost.show(selected.panes.trailingPane(for: surface))
/// The single reconciliation point. Every route that changes which surface the pane shows,
/// or which workspace it belongs to, ends here: a reveal, a hide, a workspace switch and an
/// availability change all call this, so the flag cannot describe a different connection's
/// pane than the one on screen.
syncHistoryPanelVisibility()
}

// MARK: - Session Bindings
Expand Down Expand Up @@ -1027,10 +1006,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
isTrailingPaneOpen && resolvedTrailingSurface == .assistant
}

var isHistoryVisible: Bool {
isTrailingPaneOpen && resolvedTrailingSurface == .history
}

func showInspector() {
reveal(.inspector)
}
Expand All @@ -1040,41 +1015,19 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
reveal(.assistant)
}

func showHistory() {
reveal(.history)
}

/// Auto-show follows a grid click, which is not a request for a different surface. Revealing
/// the inspector unconditionally swapped the assistant out from under a half-typed question and
/// persisted the inspector as that connection's surface, on every row the user clicked.
func revealInspectorForSelection() {
/// History is deliberately opened, the same way the assistant is, so a row click must not
/// take it away and persist the inspector as this connection's surface behind it.
guard !isAssistantVisible, !isHistoryVisible else { return }
guard !isAssistantVisible else { return }
showInspector()
}

func hideTrailingPane() {
inspectorSplitItem?.animator().isCollapsed = true
syncHistoryPanelVisibility()
recomputeWindowMinSize()
}

/// Keeps `HistoryPanelState.isVisible` saying what the window is actually showing.
///
/// The flag is not redundant with the surface. It is what `HistoryPanelView`'s `.task(id:)`
/// keys its activation on, so the view model only builds and starts querying while history is
/// on screen, and it is what the Find Past Queries tip reads to know it has been answered.
/// Leaving it behind when history moved into the trailing pane would have left the panel
/// mounted and inert, which is the shape of a pane that renders nothing forever.
internal func syncHistoryPanelVisibility() {
guard isViewLoaded, let connectionId = workspaces.selected?.connectionId else { return }
let showing = isTrailingPaneOpen && resolvedTrailingSurface == .history
let state = HistoryPanelState.forConnection(connectionId)
guard state.isVisible != showing else { return }
state.isVisible = showing
}

/// Puts the hosted child back in step with what the settings now allow.
///
/// The stored surface is left alone: a user who turns the assistant off and on again gets it
Expand All @@ -1091,7 +1044,6 @@ internal final class MainSplitViewController: NSSplitViewController, TrailingPan
rebuildTrailingPanes()
showSelectedTrailingPane()
inspectorSplitItem?.animator().isCollapsed = false
syncHistoryPanelVisibility()
recomputeWindowMinSize()
}

Expand Down
6 changes: 0 additions & 6 deletions TablePro/Core/Services/Infrastructure/TrailingPaneProxy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,8 @@ import Foundation
internal protocol TrailingPaneProxy: AnyObject {
var isInspectorVisible: Bool { get }
var isAssistantVisible: Bool { get }
var isHistoryVisible: Bool { get }
func showInspector()
func showAssistant()
func showHistory()
func hideTrailingPane()

/// Reveals the inspector for a selection the user made somewhere else, and only if that does
Expand All @@ -39,8 +37,4 @@ internal extension TrailingPaneProxy {
func toggleAssistant() {
if isAssistantVisible { hideTrailingPane() } else { showAssistant() }
}

func toggleHistory() {
if isHistoryVisible { hideTrailingPane() } else { showHistory() }
}
}
8 changes: 1 addition & 7 deletions TablePro/Core/Services/Infrastructure/WorkspacePanes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ internal final class WorkspacePanes {
/// only way it gets the `sizingOptions` firewall below, which is applied here and nowhere else.
internal let assistant: NSHostingController<AnyView>

/// Query history, on the same terms as the assistant: its own controller so the list's scroll
/// position, the selected entry and a half-typed search survive the reader looking at a row.
internal let history: NSHostingController<AnyView>

internal let sidebar: NSHostingController<AnyView>
/// The editor tab strip. It is a pane like the other three, built and kept alive per
/// connection, even though the window shows it in the titlebar accessory rather than in a
Expand All @@ -76,7 +72,6 @@ internal final class WorkspacePanes {
detail = NSHostingController(rootView: AnyView(Color.clear))
inspector = NSHostingController(rootView: AnyView(Color.clear))
assistant = NSHostingController(rootView: AnyView(Color.clear))
history = NSHostingController(rootView: AnyView(Color.clear))
sidebar = NSHostingController(rootView: AnyView(Color.clear))
tabStrip = EditorTabStripPaneController()
for pane in panes {
Expand All @@ -85,7 +80,7 @@ internal final class WorkspacePanes {
}

private var panes: [NSHostingController<AnyView>] {
[detail, inspector, assistant, history, sidebar]
[detail, inspector, assistant, sidebar]
}

/// The controller a trailing surface is drawn by. One split item hosts whichever of these the
Expand All @@ -94,7 +89,6 @@ internal final class WorkspacePanes {
switch surface {
case .inspector: inspector
case .assistant: assistant
case .history: history
}
}

Expand Down
9 changes: 0 additions & 9 deletions TablePro/Models/UI/TrailingPaneState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,6 @@ import os
let raw = defaults.string(forKey: Self.surfaceKey(connectionId)),
let stored = TrailingPaneSurface(rawValue: raw) {
self.surface = stored
} else if let connectionId, HistoryPanelPreferencesStorage.load(for: connectionId).isVisible {
/// This connection last had the query history drawer open, and the drawer is now a
/// surface of this pane. Adopting it here is what carries that reader across the move
/// instead of silently closing their history on the first launch after upgrading.
///
/// Deterministic without a migration flag: it is reachable only while no surface has
/// ever been stored for the connection, and storing one is what this initializer's
/// `didSet` does the first time anything changes it.
self.surface = .history
} else {
self.surface = .inspector
}
Expand Down
7 changes: 0 additions & 7 deletions TablePro/Models/UI/TrailingPaneSurface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,11 @@ import Foundation
internal enum TrailingPaneSurface: String, CaseIterable, Hashable {
case inspector
case assistant
/// Query history, which used to be a third band stacked under the editor and the results.
///
/// It is a peer of the other two by the same argument they are peers of each other: it is a
/// task surface no selection owns. Its panel stacks its list over its detail rather than
/// beside it, so it reads at the shared 270pt floor without moving it.
case history

internal var localizedTitle: String {
switch self {
case .inspector: String(localized: "Inspector")
case .assistant: String(localized: "Assistant")
case .history: String(localized: "Query History")
}
}

Expand Down
2 changes: 0 additions & 2 deletions TablePro/Views/Connection/TrailingPaneUnavailableView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ internal struct TrailingPaneUnavailableView: View {
return String(localized: "Row fields appear once the connection is up")
case .assistant:
return String(localized: "The assistant answers once the connection is up")
case .history:
return String(localized: "Past queries appear once the connection is up")
}
}
}
19 changes: 3 additions & 16 deletions TablePro/Views/Editor/History/HistoryPanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,24 +53,11 @@ struct HistoryPanelView: View {
}
}

/// List above, detail below.
///
/// It was list beside detail, which needed 260 + 280 of width and so could only ever live in a
/// band across the bottom of the window. The trailing pane it moved into is one shared
/// `NSSplitViewItem` with a 270pt floor, and raising that floor was measured and rejected: it
/// force-grows the pane past the width the reader chose and takes the difference from the
/// content. Rotating the split is what makes the same two panes fit, and it costs nothing,
/// because a query is a tall thing to read and a list of them is a narrow one.
///
/// The autosave name moves with the orientation. The stored value is a divider offset along the
/// axis, so restoring a horizontal position into a vertical split puts the divider somewhere
/// the reader never put it.
private func panel(_ viewModel: HistoryPanelViewModel) -> some View {
AutosavingSplitView(
autosaveName: "com.TablePro.queryHistory.listOverDetail",
isVertical: false,
primaryMinimum: 150,
secondaryMinimum: 180,
autosaveName: "com.TablePro.queryHistory.listDetail",
primaryMinimum: 260,
secondaryMinimum: 280,
collapsesPrimaryWhenTight: false
) {
HistoryListPane(
Expand Down
29 changes: 23 additions & 6 deletions TablePro/Views/Main/Child/MainEditorContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ private struct TabLoadKey: Hashable {
}

struct MainEditorContentView: View {
/// A query tab nests its own editor/results split, whose two minimums are required constraints.
/// The drawer's own minimum has to clear their sum, or dragging the drawer down asks AppKit to
/// satisfy a height the content it contains cannot reach.
static let tabContentMinimumHeight = VerticalCollapsibleSplitView<EmptyView, EmptyView>.combinedMinimumThickness

// MARK: - Dependencies

var tabManager: QueryTabManager
Expand Down Expand Up @@ -89,13 +94,25 @@ struct MainEditorContentView: View {
var body: some View {
@Bindable var historyState = HistoryPanelState.forConnection(connectionId)

return Group {
if let tab = tabManager.selectedTab {
tabContent(for: tab)
} else {
emptyStateView
return VerticalCollapsibleSplitView(
isBottomCollapsed: Binding(
get: { !historyState.isVisible },
set: { historyState.isVisible = !$0 }
),
autosaveName: SplitViewAutosaveName.historyDrawer(connectionId: connectionId),
topMinimumThickness: Self.tabContentMinimumHeight,
bottomMinimumThickness: 180,
topContent: {
if let tab = tabManager.selectedTab {
tabContent(for: tab)
} else {
emptyStateView
}
},
bottomContent: {
HistoryPanelView(coordinator: coordinator)
}
}
)
.background(.background)
.onChange(of: historyState.isVisible, initial: true) { _, isVisible in
if isVisible {
Expand Down
7 changes: 3 additions & 4 deletions TablePro/Views/Main/MainContentCommandActions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1308,11 +1308,10 @@ final class MainContentCommandActions {

// MARK: - UI Operations (Group A — Called Directly)

/// History is a trailing-pane surface, so the command that shows it is the same shape as the
/// two beside it. `HistoryPanelState.isVisible` is written by the pane rather than here, which
/// is what keeps the flag describing what the window shows instead of racing it.
func toggleHistoryPanel() {
coordinator?.trailingPaneProxy?.toggleHistory()
guard let connectionId = coordinator?.connectionId else { return }
let state = HistoryPanelState.forConnection(connectionId)
state.isVisible.toggle()
}

func toggleRightSidebar() {
Expand Down
4 changes: 2 additions & 2 deletions docs/features/keyboard-shortcuts.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,9 @@ With the sidebar focused, typing the first letters of an object's name jumps to
| Pin result | `Cmd+Option+P` |
| Close result | `Cmd+Shift+W` |

### Query history pane
### Query history drawer

With the pane open (`Cmd+Y`) and its entry list focused, `Return` loads the selected entry into the editor, `Delete` removes it, and `Cmd+C` copies its query. `Cmd+Y` hides the pane again.
With the drawer open (`Cmd+Y`) and its entry list focused, `Return` loads the selected entry into the editor, `Delete` removes it, and `Cmd+C` copies its query. `Cmd+Y` hides the drawer again.

### Inspector fields

Expand Down
Loading
Loading