import SwiftUI @main struct WorkbenchReplacementApp: App { @StateObject private var model = DatabaseModel() var body: some Scene { WindowGroup("Workbench Replacement") { WorkbenchLaunchView() .environmentObject(model) .frame(minWidth: 1000, minHeight: 650) } .commands { CommandGroup(replacing: .newItem) {} } Settings { WorkbenchSettingsView() } } } // MARK: - Application Preferences @MainActor final class WorkbenchPreferences: ObservableObject { static let shared = WorkbenchPreferences() @AppStorage("preferences.editorFontSize") var editorFontSize = 13.0 @AppStorage("preferences.uppercaseKeywords") var uppercaseKeywords = true @AppStorage("preferences.showLineNumbers") var showLineNumbers = true @AppStorage("preferences.tabWidth") var tabWidth = 4 @AppStorage("preferences.resultRowHeight") var resultRowHeight = 24.0 @AppStorage("preferences.resultColumnWidth") var resultColumnWidth = 170.0 @AppStorage("preferences.alternatingRows") var alternatingRows = true @AppStorage("preferences.appearance") var appearance = "light" @AppStorage("preferences.autoExpandTables") var autoExpandTables = true } // MARK: - Workbench Settings struct WorkbenchSettingsView: View { @AppStorage("preferences.editorFontSize") private var editorFontSize = 13.0 @AppStorage("preferences.uppercaseKeywords") private var uppercaseKeywords = true @AppStorage("preferences.showLineNumbers") private var showLineNumbers = true @AppStorage("preferences.tabWidth") private var tabWidth = 4 @AppStorage("preferences.defaultRowLimit") private var defaultRowLimit = 500 @AppStorage("preferences.resultRowHeight") private var resultRowHeight = 24.0 @AppStorage("preferences.resultColumnWidth") private var resultColumnWidth = 170.0 @AppStorage("preferences.alternatingRows") private var alternatingRows = true @AppStorage("preferences.autoExpandTables") private var autoExpandTables = true var body: some View { TabView { editorSettings .tabItem { Label( "SQL Editor", systemImage: "chevron.left.forwardslash.chevron.right" ) } executionSettings .tabItem { Label( "Query Execution", systemImage: "bolt" ) } resultSettings .tabItem { Label( "Result Grid", systemImage: "tablecells" ) } generalSettings .tabItem { Label( "General", systemImage: "gearshape" ) } } .frame(width: 620, height: 390) .preferredColorScheme(.light) } private var editorSettings: some View { settingsPage("SQL Editor") { settingsRow("Font Size") { Stepper( "\(Int(editorFontSize)) pt", value: $editorFontSize, in: 10...24, step: 1 ) .frame(width: 130) } settingsRow("Tab Width") { Picker("", selection: $tabWidth) { Text("2 spaces").tag(2) Text("4 spaces").tag(4) Text("8 spaces").tag(8) } .labelsHidden() .frame(width: 150) } settingsRow("SQL Formatting") { Toggle( "Automatically uppercase SQL keywords", isOn: $uppercaseKeywords ) } settingsRow("Editor Display") { Toggle( "Show line numbers", isOn: $showLineNumbers ) } } } private var executionSettings: some View { settingsPage("Query Execution") { settingsRow("Default Row Limit") { HStack(spacing: 12) { TextField( "", value: $defaultRowLimit, format: .number ) .textFieldStyle(.roundedBorder) .frame(width: 110) Stepper( "", value: $defaultRowLimit, in: 50...100000, step: 50 ) .labelsHidden() } } Text( "Controls the number of rows retrieved when opening a table from the schema navigator." ) .font(.system(size: 12)) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) .padding(.leading, 190) Divider() .padding(.vertical, 12) Text("Examples") .font(.headline) Text( "500 rows: Faster browsing and lower memory usage.\n" + "5,000 rows: Useful for larger datasets.\n" + "50,000 rows: May take longer to retrieve." ) .font(.system(size: 12)) .foregroundStyle(.secondary) .frame(maxWidth: .infinity, alignment: .leading) } } private var resultSettings: some View { settingsPage("Result Grid") { settingsRow("Row Height") { Stepper( "\(Int(resultRowHeight)) px", value: $resultRowHeight, in: 18...40, step: 1 ) .frame(width: 130) } settingsRow("Column Width") { Stepper( "\(Int(resultColumnWidth)) px", value: $resultColumnWidth, in: 80...400, step: 10 ) .frame(width: 140) } settingsRow("Appearance") { Toggle( "Alternating row colours", isOn: $alternatingRows ) } } } private var generalSettings: some View { settingsPage("General") { settingsRow("Navigator") { Toggle( "Automatically expand table folders", isOn: $autoExpandTables ) } } } private func settingsPage( _ title: String, @ViewBuilder content: () -> Content ) -> some View { VStack(alignment: .leading, spacing: 18) { Text(title) .font(.system(size: 20, weight: .semibold)) Divider() content() Spacer(minLength: 0) } .padding(26) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(nsColor: .windowBackgroundColor)) } private func settingsRow( _ title: String, @ViewBuilder content: () -> Content ) -> some View { HStack(alignment: .center, spacing: 16) { Text(title) .font(.system(size: 13)) .frame(width: 170, alignment: .trailing) content() Spacer(minLength: 0) } .frame(height: 30) } } // MARK: - Workbench Splash Screen struct WorkbenchLaunchView: View { @State private var showingSplash = true var body: some View { ZStack { MainView() if showingSplash { WorkbenchSplashView() .transition(.opacity) .zIndex(10) } } .task { try? await Task.sleep(for: .seconds(3)) withAnimation(.easeOut(duration: 0.4)) { showingSplash = false } } } } struct WorkbenchSplashView: View { var body: some View { VStack(spacing: 20) { Spacer() if let path = Bundle.main.path( forResource: "WorkbenchBrand", ofType: "png" ), let image = NSImage(contentsOfFile: path) { Image(nsImage: image) .resizable() .scaledToFit() .frame(width: 160, height: 160) } Text("Workbench Replacement") .font(.system(size: 30, weight: .bold)) Text("Modern MySQL Client for macOS") .font(.system(size: 15)) .foregroundStyle(.secondary) HStack(spacing: 30) { feature("cylinder.split.1x2", "Schemas") feature("chevron.left.forwardslash.chevron.right", "SQL Editor") feature("tablecells", "Result Grid") feature("server.rack", "Connections") } .padding(.top, 20) Spacer() ProgressView() .frame(width: 260) Text("Starting Workbench Replacement…") .font(.caption) .foregroundStyle(.secondary) .padding(.bottom, 30) } .frame(maxWidth: .infinity, maxHeight: .infinity) .background( LinearGradient( colors: [ Color(red: 0.96, green: 0.98, blue: 1), Color(red: 0.88, green: 0.93, blue: 1) ], startPoint: .topLeading, endPoint: .bottomTrailing ) ) .preferredColorScheme(.light) } private func feature(_ symbol: String, _ title: String) -> some View { VStack(spacing: 8) { Image(systemName: symbol) .font(.system(size: 24)) .foregroundStyle(.blue) Text(title) .font(.system(size: 12)) } .frame(width: 90) } }