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,
|
||||
|
||||
Reference in New Issue
Block a user