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 .navigationSplitViewColumnWidth( min: 220, ideal: 270, max: 400 ) } detail: { editorPanel } .navigationSplitViewStyle(.balanced) .frame(minWidth: 1000, minHeight: 650) .sheet(isPresented: $showingEditor) { ConnectionEditor( profile: $editing, password: $editingPassword ) { model.save(editing, password: editingPassword) showingEditor = false } } } // MARK: - Navigator private var navigator: some View { VStack(spacing: 0) { HStack { Text("NAVIGATOR") .font(.system(size: 11, weight: .bold)) .foregroundStyle(.secondary) Spacer() Button { model.loadDatabases() } label: { Image(systemName: "arrow.clockwise") } .buttonStyle(.borderless) .help("Refresh schemas") } .padding(12) Divider() 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 DisclosureGroup { schemaContents(db) } label: { Label( db, systemImage: "cylinder.split.1x2" ) .fontWeight( model.selectedDatabase == db ? .semibold : .regular ) .contextMenu { Button("Set as Default Schema") { model.selectedDatabase = db } Button("Refresh Tables") { model.loadTables(database: db) } } .onTapGesture { model.selectedDatabase = db if model.schemaTables[db] == nil { model.loadTables(database: db) } } } } } } .listStyle(.sidebar) Divider() HStack { Button { editing = ConnectionProfile( name: "New Connection" ) editingPassword = "" showingEditor = true } label: { Label( "New Connection", systemImage: "plus" ) } .buttonStyle(.borderless) Spacer() } .padding(10) } } @ViewBuilder private func schemaContents(_ database: String) -> some View { if let tables = model.schemaTables[database] { if tables.isEmpty { Text("No tables") .foregroundStyle(.secondary) } else { ForEach(tables, id: \.self) { table in Label(table, systemImage: "tablecells") .font(.system(size: 12)) .onTapGesture(count: 2) { model.selectTable( database: database, table: table ) } .contextMenu { Button("Select First 100 Rows") { model.selectTable( database: database, table: table ) } } } } } else { Button("Load Tables") { model.loadTables(database: database) } .buttonStyle(.borderless) } } // MARK: - SQL Editor private var editorPanel: some View { VStack(spacing: 0) { editorToolbar Divider() VSplitView { sqlEditor .frame(minHeight: 180) resultsPanel .frame(minHeight: 180) } Divider() statusBar } .navigationTitle("WorkbenchReplacement") } private var editorToolbar: some View { HStack(spacing: 14) { Label( "SQL Editor", systemImage: "chevron.left.forwardslash.chevron.right" ) .font(.headline) Divider() .frame(height: 20) Button { model.runSQL() } label: { Label( "Execute", systemImage: "bolt.fill" ) } .keyboardShortcut( .return, modifiers: [.command] ) .disabled(model.isRunning) Button { model.formatSQL() } label: { Label( "Format SQL", systemImage: "text.alignleft" ) } Spacer() if !model.selectedDatabase.isEmpty { Label( model.selectedDatabase, systemImage: "cylinder" ) .foregroundStyle(.secondary) } Button { model.loadDatabases() } label: { Image(systemName: "arrow.clockwise") } .help("Refresh schemas") } .buttonStyle(.borderless) .padding(12) } private var sqlEditor: some View { VStack(alignment: .leading, spacing: 0) { HStack { Image(systemName: "doc.text") Text("Query 1") .fontWeight(.medium) Spacer() Text("MySQL") .foregroundStyle(.secondary) } .font(.system(size: 12)) .padding(.horizontal, 12) .padding(.vertical, 8) Divider() TextEditor(text: $model.sql) .font( .system( size: 14, design: .monospaced ) ) .scrollContentBackground(.hidden) .padding(12) .background( Color(nsColor: .textBackgroundColor) ) } } // MARK: - Results private var resultsPanel: some View { VStack(spacing: 0) { HStack { Label( "Result Grid", systemImage: "tablecells" ) .font(.headline) Spacer() Text(model.result.message) .font(.caption) .foregroundStyle(.secondary) } .padding(12) Divider() if model.result.columns.isEmpty { ContentUnavailableView( "No Results", systemImage: "tablecells", description: Text( "Execute a SQL query to display results." ) ) } else { resultsGrid } } } private var resultsGrid: some View { ScrollView([.horizontal, .vertical]) { Grid( alignment: .leading, horizontalSpacing: 0, verticalSpacing: 0 ) { GridRow { ForEach( model.result.columns.indices, id: \.self ) { index in Text(model.result.columns[index]) .fontWeight(.semibold) .frame( minWidth: 150, alignment: .leading ) .padding(8) .background( Color(nsColor: .controlBackgroundColor) ) } } ForEach( model.result.rows.indices, id: \.self ) { rowIndex in resultRow( model.result.rows[rowIndex], index: rowIndex ) } } } } private func resultRow( _ row: [String], index: Int ) -> some View { GridRow { ForEach( model.result.columns.indices, id: \.self ) { columnIndex in Text( columnIndex < row.count ? row[columnIndex] : "" ) .font( .system( size: 12, design: .monospaced ) ) .frame( minWidth: 150, alignment: .leading ) .padding(8) .background( index.isMultiple(of: 2) ? Color.clear : Color(nsColor: .alternatingContentBackgroundColors[0]) ) } } } // MARK: - Status 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 } } // MARK: - Connection Editor 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( "Native MySQL connection. Local TCP connections and SSH tunnels are supported." ) .font(.caption) .foregroundStyle(.secondary) HStack { Spacer() Button("Cancel") { dismiss() } Button("Save") { save() } .buttonStyle(.borderedProminent) } } .padding(24) .frame(width: 520) } }