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:
@@ -1,6 +1,10 @@
|
||||
import SwiftUI
|
||||
import AppKit
|
||||
|
||||
extension Notification.Name {
|
||||
static let workbenchRevealSQLEditor = Notification.Name("WorkbenchReplacement.RevealSQLEditor")
|
||||
}
|
||||
|
||||
@main
|
||||
struct WorkbenchReplacementApp: App {
|
||||
@StateObject private var model = DatabaseModel()
|
||||
@@ -410,6 +414,7 @@ struct WorkbenchFileCommands: Commands {
|
||||
CommandGroup(replacing: .newItem) {
|
||||
Button("New Query Tab") {
|
||||
model.newQueryTab()
|
||||
NotificationCenter.default.post(name: .workbenchRevealSQLEditor, object: nil)
|
||||
}
|
||||
.keyboardShortcut("t", modifiers: .command)
|
||||
}
|
||||
@@ -420,6 +425,21 @@ struct WorkbenchFileCommands: Commands {
|
||||
}
|
||||
.keyboardShortcut("o", modifiers: .command)
|
||||
|
||||
Menu("Open Recent SQL Script") {
|
||||
if model.recentSQLScripts.isEmpty {
|
||||
Text("No Recent Scripts")
|
||||
} else {
|
||||
ForEach(model.recentSQLScripts, id: \.self) { path in
|
||||
Button(URL(fileURLWithPath: path).lastPathComponent) {
|
||||
model.openRecentSQLScript(at: path)
|
||||
}
|
||||
.help(path)
|
||||
}
|
||||
Divider()
|
||||
Button("Clear Recent Scripts") { model.clearRecentSQLScripts() }
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Save SQL Script") {
|
||||
|
||||
@@ -87,6 +87,38 @@ final class DatabaseModel: ObservableObject {
|
||||
private let historyKey = "WorkbenchReplacement.queryHistory.v1"
|
||||
private let savedQueriesKey = "WorkbenchReplacement.savedQueries.v1"
|
||||
private let maxHistoryEntries = 200
|
||||
private let recentScriptsKey = "WorkbenchReplacement.recentSQLScripts.v1"
|
||||
@Published private(set) var recentSQLScripts: [String] = []
|
||||
|
||||
private func recordRecentSQLScript(_ url: URL) {
|
||||
let path = url.standardizedFileURL.path
|
||||
recentSQLScripts.removeAll { $0 == path }
|
||||
recentSQLScripts.insert(path, at: 0)
|
||||
recentSQLScripts = Array(recentSQLScripts.prefix(12))
|
||||
UserDefaults.standard.set(recentSQLScripts, forKey: recentScriptsKey)
|
||||
}
|
||||
|
||||
func clearRecentSQLScripts() {
|
||||
recentSQLScripts = []
|
||||
UserDefaults.standard.removeObject(forKey: recentScriptsKey)
|
||||
}
|
||||
|
||||
func openRecentSQLScript(at path: String) {
|
||||
let url = URL(fileURLWithPath: path)
|
||||
do {
|
||||
let contents = try String(contentsOf: url, encoding: .utf8)
|
||||
saveActiveQuery()
|
||||
let tab = WorkbenchQueryTab(title: url.lastPathComponent, sql: contents, fileURL: url)
|
||||
queryTabs.append(tab)
|
||||
activateQuery(tab.id)
|
||||
recordRecentSQLScript(url)
|
||||
status = "Opened \(url.lastPathComponent)"
|
||||
NotificationCenter.default.post(name: .workbenchRevealSQLEditor, object: nil)
|
||||
} catch {
|
||||
status = "Could not open \(url.lastPathComponent): \(error.localizedDescription)"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func saveCurrentQuery(named name: String) {
|
||||
let clean = name.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
@@ -153,6 +185,7 @@ final class DatabaseModel: ObservableObject {
|
||||
private let storageKey = "WorkbenchReplacement.connectionProfiles.native.v1"
|
||||
|
||||
init() {
|
||||
recentSQLScripts = UserDefaults.standard.stringArray(forKey: recentScriptsKey) ?? []
|
||||
if let data = UserDefaults.standard.data(forKey: historyKey),
|
||||
let entries = try? JSONDecoder().decode([SQLHistoryEntry].self, from: data) {
|
||||
queryHistory = Array(entries.prefix(maxHistoryEntries))
|
||||
@@ -171,7 +204,7 @@ final class DatabaseModel: ObservableObject {
|
||||
func newQueryTab() {
|
||||
saveActiveQuery()
|
||||
let tab = WorkbenchQueryTab(
|
||||
title: "Query \\(queryTabs.count + 1)"
|
||||
title: "Query \(queryTabs.count + 1)"
|
||||
)
|
||||
queryTabs.append(tab)
|
||||
activeQueryID = tab.id
|
||||
@@ -235,6 +268,8 @@ final class DatabaseModel: ObservableObject {
|
||||
|
||||
queryTabs.append(tab)
|
||||
activateQuery(tab.id)
|
||||
recordRecentSQLScript(url)
|
||||
NotificationCenter.default.post(name: .workbenchRevealSQLEditor, object: nil)
|
||||
status = "Opened \\(url.lastPathComponent)"
|
||||
}
|
||||
|
||||
@@ -262,6 +297,7 @@ final class DatabaseModel: ObservableObject {
|
||||
try sql.write(to: url, atomically: true, encoding: .utf8)
|
||||
queryTabs[index].fileURL = url
|
||||
queryTabs[index].title = url.lastPathComponent
|
||||
recordRecentSQLScript(url)
|
||||
status = "Saved \\(url.lastPathComponent)"
|
||||
} catch {
|
||||
status = "Save failed: \\(error.localizedDescription)"
|
||||
|
||||
@@ -89,6 +89,11 @@ struct MainView: View {
|
||||
) == "dark" ? .dark : .light
|
||||
)
|
||||
.background(Color(nsColor: .windowBackgroundColor))
|
||||
.onReceive(NotificationCenter.default.publisher(for: .workbenchRevealSQLEditor)) { _ in
|
||||
administrationPage = nil
|
||||
navigatorMode = 1
|
||||
DispatchQueue.main.async { sqlFocusRequest += 1 }
|
||||
}
|
||||
.task {
|
||||
if model.databases.isEmpty, model.selected != nil {
|
||||
model.loadDatabases()
|
||||
@@ -842,8 +847,40 @@ struct MainView: View {
|
||||
}
|
||||
.help("Format SQL")
|
||||
|
||||
Divider()
|
||||
.frame(height: 17)
|
||||
Divider().frame(height: 17)
|
||||
|
||||
Button {
|
||||
model.openSQLScript()
|
||||
} label: {
|
||||
Image(systemName: "folder")
|
||||
}
|
||||
.help("Open SQL Script…")
|
||||
|
||||
Button {
|
||||
model.saveSQLScript()
|
||||
} label: {
|
||||
Image(systemName: "square.and.arrow.down")
|
||||
}
|
||||
.help("Save SQL Script")
|
||||
|
||||
Menu {
|
||||
if model.recentSQLScripts.isEmpty {
|
||||
Text("No Recent Scripts")
|
||||
} else {
|
||||
ForEach(model.recentSQLScripts, id: \.self) { path in
|
||||
Button(URL(fileURLWithPath: path).lastPathComponent) {
|
||||
model.openRecentSQLScript(at: path)
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
Button("Clear Recent Scripts") { model.clearRecentSQLScripts() }
|
||||
}
|
||||
} label: {
|
||||
Image(systemName: "clock.arrow.circlepath")
|
||||
}
|
||||
.help("Recent SQL Scripts")
|
||||
|
||||
Divider().frame(height: 17)
|
||||
|
||||
Button {
|
||||
showingQueryHistory = true
|
||||
|
||||
Reference in New Issue
Block a user