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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user