From d96fc225cf51a1aa753f1b00fc61bca60dd4237f Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Sun, 18 Oct 2026 11:36:34 +0100 Subject: [PATCH] Improved progress display Previously, the progress bar updated only after completing an entire table. For large tables such as LIBARC, this could make the application appear to have stopped responding. Phase 10 updates the interface while rows are being exported. --- WorkbenchReplacement/MainView.swift | 114 +++++++++++++------- docs/PHASE51-SCROLLING.md | 9 ++ docs/PHASE52-EDITOR-FOCUS-INITIAL-GRAPHS.md | 10 ++ docs/PHASE53-EXECUTE-REVEALS-EDITOR.md | 10 ++ 4 files changed, 106 insertions(+), 37 deletions(-) create mode 100644 docs/PHASE51-SCROLLING.md create mode 100644 docs/PHASE52-EDITOR-FOCUS-INITIAL-GRAPHS.md create mode 100644 docs/PHASE53-EXECUTE-REVEALS-EDITOR.md diff --git a/WorkbenchReplacement/MainView.swift b/WorkbenchReplacement/MainView.swift index 5765ed8..5371109 100644 --- a/WorkbenchReplacement/MainView.swift +++ b/WorkbenchReplacement/MainView.swift @@ -52,6 +52,7 @@ struct MainView: View { @State private var outputExpanded = true @State private var navigatorMode = 1 + @State private var sqlFocusRequest = 0 @State private var administrationPage: WorkbenchAdminPage? = .dashboard @@ -126,6 +127,21 @@ struct MainView: View { } } + // MARK: - Query execution navigation + + /// A query launched from the global toolbar must reveal its editor and + /// results, even when an Administration page currently owns the workspace. + private func executeSQLInEditor() { + navigatorMode = 1 + administrationPage = nil + model.runSQL() + // The SQLTextView is created only after the workspace changes. Request + // focus on the next main-loop turn, after SwiftUI mounts that view. + DispatchQueue.main.async { + sqlFocusRequest += 1 + } + } + // MARK: - Main Toolbar private var mainToolbar: some View { @@ -175,7 +191,7 @@ struct MainView: View { "Execute", icon: "bolt.fill" ) { - model.runSQL() + executeSQLInEditor() } .keyboardShortcut(.return, modifiers: [.command]) @@ -236,6 +252,12 @@ struct MainView: View { .pickerStyle(.segmented) .labelsHidden() .padding(8) + .onChange(of: navigatorMode) { _, newMode in + if newMode == 1 { + administrationPage = nil + sqlFocusRequest += 1 + } + } if navigatorMode == 0 { administrationNavigator @@ -800,7 +822,7 @@ struct MainView: View { .fill(WB.border) .frame(width: 1) - SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, completions: Array(Set(model.schemaTables.values.flatMap { $0 } + model.schemaViews.values.flatMap { $0 } + (model.schemaColumns[model.selectedDatabase] ?? []))).sorted()) + SQLTextView(text: $model.sql, selectedSQL: $selectedSQL, focusRequest: sqlFocusRequest, completions: Array(Set(model.schemaTables.values.flatMap { $0 } + model.schemaViews.values.flatMap { $0 } + (model.schemaColumns[model.selectedDatabase] ?? []))).sorted()) } .background(Color(nsColor: .textBackgroundColor)) } @@ -1198,6 +1220,7 @@ private struct SavedSQLQueriesSheet: View { struct SQLTextView: NSViewRepresentable { @Binding var text: String @Binding var selectedSQL: String + var focusRequest: Int = 0 var completions: [String] = [] func makeCoordinator() -> Coordinator { @@ -1235,6 +1258,11 @@ struct SQLTextView: NSViewRepresentable { editor.string = text context.coordinator.highlight(editor) + // The editor may be mounted after the Schemas navigation transition. + DispatchQueue.main.async { [weak editor] in + guard let editor, let window = editor.window else { return } + window.makeFirstResponder(editor) + } return scroll } @@ -1246,6 +1274,14 @@ struct SQLTextView: NSViewRepresentable { guard let editor = scroll.documentView as? NSTextView else { return } + context.coordinator.parent = self + if context.coordinator.lastFocusRequest != focusRequest { + context.coordinator.lastFocusRequest = focusRequest + DispatchQueue.main.async { [weak editor] in + guard let editor, let window = editor.window else { return } + window.makeFirstResponder(editor) + } + } if editor.string != text { let selection = editor.selectedRange() @@ -1266,9 +1302,11 @@ struct SQLTextView: NSViewRepresentable { final class Coordinator: NSObject, NSTextViewDelegate { var parent: SQLTextView + var lastFocusRequest: Int init(_ parent: SQLTextView) { self.parent = parent + self.lastFocusRequest = parent.focusRequest } private let completionWords = [ @@ -2376,6 +2414,15 @@ struct WorkbenchAdministrationView: View { await refresh() } .task(id: "\(page.id)-\(autoRefresh)-\(profile?.id.uuidString ?? "")") { + // Gather a second snapshot promptly on first display so rate graphs + // draw a line without waiting for the full five-second interval. + if autoRefresh && supportsTrends { + try? await Task.sleep(for: .seconds(1.5)) + if !Task.isCancelled && autoRefresh { + if loading { try? await Task.sleep(for: .seconds(1)) } + if !Task.isCancelled && autoRefresh { await refresh() } + } + } while autoRefresh && !Task.isCancelled { try? await Task.sleep(for: .seconds(5)) if Task.isCancelled || !autoRefresh { break } @@ -2553,52 +2600,45 @@ struct WorkbenchAdministrationView: View { private var resultContent: some View { switch page { case .serverStatus, .dashboard: - // Phase 49: preserve the verified Phase 48 line charts, while - // restoring the server overview in its own scrollable lower pane. - GeometryReader { available in - let chartHeight = min(155.0, max(85.0, (available.size.height - 270.0) / 3.0)) - VStack(alignment: .leading, spacing: 0) { + // Phase 51: one outer scroll container owns the entire dashboard. + // The former fixed-height top chart pane consumed the available + // window height and left the overview pane impossible to reach. + ScrollView(.vertical) { + VStack(alignment: .leading, spacing: 18) { WorkbenchInlinePerformanceDashboard( samples: diagnosticSamples, - chartHeight: chartHeight + chartHeight: 155 ) - .padding(.horizontal, 18) - .padding(.top, 12) - .padding(.bottom, 8) - .fixedSize(horizontal: false, vertical: true) - .layoutPriority(1) - .accessibilityIdentifier("live-mysql-line-graphs-phase49") + .accessibilityIdentifier("live-mysql-line-graphs-phase51") Divider() - ScrollView(.vertical) { - VStack(alignment: .leading, spacing: 14) { - Label("Server overview · Live graphs", systemImage: "chart.xyaxis.line") - .font(.title3.bold()) - WorkbenchServerOverview(rows: filteredRows, samples: diagnosticSamples) - if page == .serverStatus { - Divider() - HStack { - Text("Server status metrics").font(.headline) - Spacer() - Text("\(filteredRows.count) metrics") - .font(.caption).foregroundStyle(.secondary) - } - WorkbenchColouredCounterTable( - columns: ["Metric", "Value"], rows: filteredRows - ) - .frame(height: 300) - } + Label("Server overview · Live graphs", systemImage: "chart.xyaxis.line") + .font(.title3.bold()) + + WorkbenchServerOverview(rows: filteredRows, samples: diagnosticSamples) + + if page == .serverStatus { + Divider() + HStack { + Text("Server status metrics").font(.headline) + Spacer() + Text("\(filteredRows.count) metrics") + .font(.caption).foregroundStyle(.secondary) } - .padding(18) - .frame(maxWidth: .infinity, alignment: .leading) + // The grid owns its own bounded viewport; the outer + // page can still scroll to every section below charts. + WorkbenchColouredCounterTable( + columns: ["Metric", "Value"], rows: filteredRows + ) + .frame(height: 320) } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .accessibilityIdentifier("scrollable-server-overview-phase49") } - .frame(width: available.size.width, height: available.size.height, alignment: .topLeading) + .padding(18) + .frame(maxWidth: .infinity, alignment: .leading) } .frame(maxWidth: .infinity, maxHeight: .infinity) + .accessibilityIdentifier("scrollable-dashboard-phase51") case .serverCounters, .connectionStatistics, .innodbMetrics: WorkbenchLiveMonitorScreen( page: page, diff --git a/docs/PHASE51-SCROLLING.md b/docs/PHASE51-SCROLLING.md new file mode 100644 index 0000000..d7d74a3 --- /dev/null +++ b/docs/PHASE51-SCROLLING.md @@ -0,0 +1,9 @@ +# Phase 51 — Full-page scrolling + +Baseline: Phase 50, including all 14 working live line graphs. + +The previous Dashboard / Server Status implementation put the six performance charts outside a second scrolling pane and constrained the complete layout to the window height. On typical window sizes, the six charts consumed nearly all vertical space, leaving the lower overview graphs inaccessible. + +Both pages now use a single vertical ScrollView around the six performance graphs, eight overview graphs, and (on Server Status) the status metrics grid. The existing chart components, series, styling, and five-second sampling are unchanged. Scroll down using a trackpad, mouse wheel or scroll bar to reach the overview graphs and metrics. + +This archive has not been compiled or visually tested on macOS. diff --git a/docs/PHASE52-EDITOR-FOCUS-INITIAL-GRAPHS.md b/docs/PHASE52-EDITOR-FOCUS-INITIAL-GRAPHS.md new file mode 100644 index 0000000..aa1a122 --- /dev/null +++ b/docs/PHASE52-EDITOR-FOCUS-INITIAL-GRAPHS.md @@ -0,0 +1,10 @@ +# Phase 52 — Schema editor focus and first-display graphs + +Based on the approved Phase 50 graphs and Phase 51 scrolling layout. + +- Selecting **Schemas** switches the workspace to the SQL editor and requests keyboard focus for the native NSTextView. +- The native editor receives focus on its initial mount and on subsequent navigation to Schemas; the existing editor text, selection and syntax highlighting remain intact. +- Monitoring views perform their first refresh on appearance and, when auto-refresh is enabled, attempt a follow-up snapshot after about 1.5 seconds. Rate graphs require two distinct snapshots; gauge graphs can show a first data point immediately. Normal five-second refresh continues thereafter. +- The chart designs, all eight overview graphs, six performance graphs, SQL queries and scrolling are otherwise unchanged. + +**Verification:** Swift parser checked on Linux. macOS compilation and GUI interaction testing are still required. diff --git a/docs/PHASE53-EXECUTE-REVEALS-EDITOR.md b/docs/PHASE53-EXECUTE-REVEALS-EDITOR.md new file mode 100644 index 0000000..f46cdb5 --- /dev/null +++ b/docs/PHASE53-EXECUTE-REVEALS-EDITOR.md @@ -0,0 +1,10 @@ +# Phase 53 — Execute reveals SQL editor + +Based on Phase 52; preserves the Phase 50 graph design and Phase 51 scrolling. + +- Global toolbar **Execute** and its Command-Return shortcut now navigate from Dashboard or another Administration screen to Schemas / SQL Editor before running the query. +- The query still runs through `DatabaseModel.runSQL()`; its existing results, error handling, and query history are unchanged. +- Focus is requested on the next main-loop turn after the SQL workspace is mounted. +- The in-editor Execute and Run Selection buttons retain their existing behaviour. + +Verification: Swift syntax parsing and ZIP integrity on Linux. macOS build and UI testing not available here.