This commit is contained in:
Mark Gregory
2026-10-16 21:34:30 +01:00
parent 2aedde9fe2
commit 773191f206
39 changed files with 324 additions and 252 deletions
+138
View File
@@ -1,4 +1,5 @@
import Foundation
import AppKit
import Security
struct ConnectionProfile: Identifiable, Codable, Hashable {
@@ -39,10 +40,40 @@ enum PasswordStore {
}
}
struct WorkbenchQueryTab: Identifiable {
let id: UUID
var title: String
var sql: String
var result: QueryResult
var fileURL: URL?
init(
id: UUID = UUID(),
title: String,
sql: String = "",
result: QueryResult = QueryResult(),
fileURL: URL? = nil
) {
self.id = id
self.title = title
self.sql = sql
self.result = result
self.fileURL = fileURL
}
}
@MainActor
final class DatabaseModel: ObservableObject {
@Published var profiles: [ConnectionProfile] = []
@Published var selectedID: UUID?
@Published var queryTabs: [WorkbenchQueryTab] = [
WorkbenchQueryTab(
title: "Query 1",
sql: "SELECT VERSION() AS server_version;"
)
]
@Published var activeQueryID: UUID?
@Published var sql = "SELECT VERSION() AS server_version;"
@Published var result = QueryResult()
@Published var databases: [String] = []
@@ -58,7 +89,109 @@ final class DatabaseModel: ObservableObject {
let decoded = try? JSONDecoder().decode([ConnectionProfile].self, from: data) { profiles = decoded }
if profiles.isEmpty { profiles = [ConnectionProfile()] }
selectedID = profiles.first?.id
activeQueryID = queryTabs.first?.id
}
func newQueryTab() {
saveActiveQuery()
let tab = WorkbenchQueryTab(
title: "Query \\(queryTabs.count + 1)"
)
queryTabs.append(tab)
activeQueryID = tab.id
sql = tab.sql
result = tab.result
}
func saveActiveQuery() {
guard let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id })
else { return }
queryTabs[index].sql = sql
queryTabs[index].result = result
}
func activateQuery(_ id: UUID) {
saveActiveQuery()
guard let tab = queryTabs.first(where: { $0.id == id })
else { return }
activeQueryID = tab.id
sql = tab.sql
result = tab.result
}
func closeQuery(_ id: UUID) {
saveActiveQuery()
queryTabs.removeAll { $0.id == id }
if queryTabs.isEmpty {
let tab = WorkbenchQueryTab(title: "Query 1")
queryTabs.append(tab)
}
if activeQueryID == id {
activeQueryID = nil
activateQuery(queryTabs[0].id)
}
}
func openSQLScript() {
let panel = NSOpenPanel()
panel.allowedContentTypes = [.plainText]
panel.allowsOtherFileTypes = true
panel.canChooseDirectories = false
guard panel.runModal() == .OK,
let url = panel.url,
let contents = try? String(contentsOf: url, encoding: .utf8)
else { return }
saveActiveQuery()
let tab = WorkbenchQueryTab(
title: url.lastPathComponent,
sql: contents,
fileURL: url
)
queryTabs.append(tab)
activateQuery(tab.id)
status = "Opened \\(url.lastPathComponent)"
}
func saveSQLScript(asNewFile: Bool = false) {
saveActiveQuery()
guard let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id })
else { return }
var destination = asNewFile ? nil : queryTabs[index].fileURL
if destination == nil {
let panel = NSSavePanel()
panel.nameFieldStringValue = "query.sql"
panel.canCreateDirectories = true
guard panel.runModal() == .OK else { return }
destination = panel.url
}
guard let url = destination else { return }
do {
try sql.write(to: url, atomically: true, encoding: .utf8)
queryTabs[index].fileURL = url
queryTabs[index].title = url.lastPathComponent
status = "Saved \\(url.lastPathComponent)"
} catch {
status = "Save failed: \\(error.localizedDescription)"
}
}
var selected: ConnectionProfile? { profiles.first { $0.id == selectedID } }
func save(_ profile: ConnectionProfile, password: String) {
if let i = profiles.firstIndex(where: { $0.id == profile.id }) { profiles[i] = profile }
@@ -178,6 +311,11 @@ final class DatabaseModel: ObservableObject {
status = "Connected to \(profile.name)"
} else {
result = parsed
if let id = activeQueryID,
let index = queryTabs.firstIndex(where: { $0.id == id }) {
queryTabs[index].sql = sql
queryTabs[index].result = parsed
}
status = "Query completed (\(parsed.rows.count) rows)"
}
} catch {