Files
SQLWorkbench/WorkbenchReplacement/MainView.swift.backup-20261016-165756
T
2026-10-16 17:03:05 +01:00

190 lines
6.3 KiB
Plaintext

import SwiftUI
struct MainView: View {
@EnvironmentObject private var model: DatabaseModel
@State private var showingEditor = false
@State private var editing = ConnectionProfile()
@State private var editingPassword = ""
var body: some View {
NavigationSplitView {
navigator
} detail: {
editorPanel
}
.sheet(isPresented: $showingEditor) {
ConnectionEditor(profile: $editing, password: $editingPassword) {
model.save(editing, password: editingPassword)
showingEditor = false
}
}
}
private var navigator: some View {
List(selection: $model.selectedID) {
Section("MYSQL CONNECTIONS") {
ForEach(model.profiles) { profile in
Label(profile.name, systemImage: "server.rack")
.tag(profile.id)
.contextMenu {
Button("Edit Connection") { edit(profile) }
}
}
}
Section("SCHEMAS") {
ForEach(model.databases, id: \.self) { db in
Button {
model.selectedDatabase = db
} label: {
Label(db, systemImage: "cylinder.split.1x2")
}
.buttonStyle(.plain)
}
}
}
.listStyle(.sidebar)
.navigationTitle("Navigator")
.toolbar {
Button {
editing = ConnectionProfile(name: "New Connection")
editingPassword = ""
showingEditor = true
} label: {
Image(systemName: "plus")
}
.help("New connection")
Button {
model.loadDatabases()
} label: {
Image(systemName: "arrow.clockwise")
}
.help("Connect / refresh schemas")
}
}
private var editorPanel: some View {
VStack(spacing: 0) {
editorToolbar
Divider()
TextEditor(text: $model.sql)
.font(.system(size: 14, design: .monospaced))
.frame(minHeight: 190)
.padding(8)
Divider()
resultsPanel
Divider()
statusBar
}
.navigationTitle("Workbench Replacement")
}
private var editorToolbar: some View {
HStack {
Label("SQL Editor", systemImage: "chevron.left.forwardslash.chevron.right")
.font(.headline)
Spacer()
Button("Edit Connection") {
if let profile = model.selected { edit(profile) }
}
Button("Execute") { model.runSQL() }
.keyboardShortcut(.return, modifiers: [.command])
.disabled(model.isRunning)
}
.padding(12)
}
private var resultsPanel: some View {
VStack(spacing: 0) {
HStack {
Text("Result Grid").font(.headline)
Spacer()
Text(model.result.message).foregroundStyle(.secondary)
}
.padding(12)
if model.result.columns.isEmpty {
ContentUnavailableView("No Results", systemImage: "tablecells",
description: Text("Run a query to display results."))
} else {
resultsGrid
}
}
}
private var resultsGrid: some View {
ScrollView([.horizontal, .vertical]) {
Grid(alignment: .leading, horizontalSpacing: 14, verticalSpacing: 8) {
GridRow {
ForEach(model.result.columns, id: \.self) { name in
Text(name)
.fontWeight(.semibold)
.frame(minWidth: 130, alignment: .leading)
}
}
ForEach(model.result.rows.indices, id: \.self) { rowIndex in
resultRow(model.result.rows[rowIndex])
}
}
.padding(12)
}
}
private func resultRow(_ row: [String]) -> some View {
GridRow {
ForEach(model.result.columns.indices, id: \.self) { index in
Text(index < row.count ? row[index] : "")
.font(.system(.body, design: .monospaced))
.frame(minWidth: 130, alignment: .leading)
}
}
}
private var statusBar: some View {
HStack {
Circle()
.fill(model.status == "Error" ? Color.red : Color.green)
.frame(width: 8, height: 8)
Text(model.status).lineLimit(1)
Spacer()
Text(model.selected?.name ?? "No connection")
}
.font(.caption)
.padding(9)
}
private func edit(_ profile: ConnectionProfile) {
editing = profile
editingPassword = PasswordStore.read(profile.id)
showingEditor = true
}
}
struct ConnectionEditor: View {
@Binding var profile: ConnectionProfile
@Binding var password: String
let save: () -> Void
@Environment(\.dismiss) private var dismiss
var body: some View {
VStack(alignment: .leading, spacing: 14) {
Text("Connection Settings").font(.title2.bold())
Form {
TextField("Connection Name", text: $profile.name)
TextField("Hostname", text: $profile.host)
TextField("Port", value: $profile.port, format: .number)
TextField("Username", text: $profile.username)
SecureField("Password (stored in macOS Keychain)", text: $password)
TextField("Default Schema", text: $profile.database)
}
Text("Built-in MySQL protocol client. This first version supports loopback TCP connections and local SSH tunnels. Remote TLS and full caching_sha2 authentication are not implemented yet.")
.font(.caption).foregroundStyle(.secondary)
HStack {
Spacer()
Button("Cancel") { dismiss() }
Button("Save") { save() }.buttonStyle(.borderedProminent)
}
}
.padding(24)
.frame(width: 520)
}
}