From ab8783a1f77d0730a8e8d0fea7d4dce6d75c523d Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Mon, 19 Oct 2026 10:14:12 +0100 Subject: [PATCH] new tests --- DBSyn/App.swift | 2 +- DBSyn/LaravelMigrationView.swift | 77 +++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 12 deletions(-) diff --git a/DBSyn/App.swift b/DBSyn/App.swift index bc1aa23..b8da462 100644 --- a/DBSyn/App.swift +++ b/DBSyn/App.swift @@ -33,7 +33,7 @@ struct DBSynApp: App { let icon = NSImage(contentsOfFile: Bundle.main.path(forResource: "DBSynBrand", ofType: "png") ?? "") NSApplication.shared.orderFrontStandardAboutPanel(options: [ .applicationName: "DBSyn", - .applicationIcon: icon ?? NSApplication.shared.applicationIconImage ?? NSImage(), + .applicationIcon: icon ?? NSApplication.shared.applicationIconImage, .version: Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as? String ?? "1.0", .credits: NSAttributedString(string: "Native MySQL client for macOS") ]) diff --git a/DBSyn/LaravelMigrationView.swift b/DBSyn/LaravelMigrationView.swift index 88c6cf9..e19f146 100644 --- a/DBSyn/LaravelMigrationView.swift +++ b/DBSyn/LaravelMigrationView.swift @@ -124,6 +124,7 @@ struct LaravelMigrationView: View { @State private var generated = "" @State private var errorText = "" @State private var tableName = "" + @State private var chosenDatabase = "" @State private var isLoading = false var body: some View { VStack(alignment: .leading, spacing: 14) { @@ -135,11 +136,46 @@ struct LaravelMigrationView: View { } Spacer() } - HStack { - TextField("Table name", text: $tableName).frame(maxWidth: 260) - Button("Load from Connected Database") { loadTable() }.disabled(tableName.isEmpty || model.selected == nil || isLoading) - Button("Open SQL File…") { openFile() } - Spacer() + VStack(alignment: .leading, spacing: 10) { + HStack(spacing: 12) { + Text("Database").frame(width: 80, alignment: .leading) + Picker("Database", selection: $chosenDatabase) { + Text("Select database…").tag("") + ForEach(databaseOptions, id: \.self) { name in + Text(name).tag(name) + } + } + .labelsHidden() + .frame(maxWidth: 330) + Button("Use Current Database") { + chosenDatabase = model.completionDatabase + } + .disabled(model.completionDatabase.isEmpty) + Spacer() + } + HStack(spacing: 12) { + Text("Table").frame(width: 80, alignment: .leading) + TextField("Enter a table name", text: $tableName) + .frame(maxWidth: 330) + if !availableTables.isEmpty { + Picker("Choose table", selection: $tableName) { + Text("Choose table…").tag("") + ForEach(availableTables, id: \.self) { name in + Text(name).tag(name) + } + } + .labelsHidden() + .frame(maxWidth: 230) + } + Button(isLoading ? "Loading…" : "Load Table") { loadTable() } + .disabled(tableName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || chosenDatabase.isEmpty || model.selected == nil || isLoading) + Button("Open SQL File…") { openFile() } + Spacer() + } + if chosenDatabase.isEmpty { + Text("Choose a database before loading a table. SQL files can be opened without a connection.") + .font(.caption).foregroundStyle(.secondary) + } } HSplitView { VStack(alignment: .leading) { @@ -161,7 +197,19 @@ struct LaravelMigrationView: View { } } .padding(22) - .frame(minWidth: 850, minHeight: 600) + .frame(minWidth: 980, minHeight: 650) + .onAppear { + if chosenDatabase.isEmpty { + chosenDatabase = model.completionDatabase + } + } + .onChange(of: chosenDatabase) { _, _ in tableName = "" } + } + private var databaseOptions: [String] { + Array(Set(model.databases + model.schemaTables.keys + [model.completionDatabase].filter { !$0.isEmpty })).sorted() + } + private var availableTables: [String] { + model.schemaTables[chosenDatabase] ?? [] } private func generate() { do { generated = try LaravelMigrationGenerator.generate(sourceSQL); errorText = "" } @@ -177,14 +225,21 @@ struct LaravelMigrationView: View { private func loadTable() { guard let profile = model.selected else { return } isLoading = true - let db = model.selectedDatabase.isEmpty ? profile.database : model.selectedDatabase - let escaped = tableName.replacingOccurrences(of: "`", with: "``") + let db = chosenDatabase.trimmingCharacters(in: .whitespacesAndNewlines) + guard !db.isEmpty else { + errorText = "Select a database before loading a table." + isLoading = false + return + } + let escaped = tableName.trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "`", with: "``") + let escapedDB = db.replacingOccurrences(of: "`", with: "``") + errorText = "" Task { do { - let result = try await NativeMySQLRunner.run(profile: profile, database: db, password: PasswordStore.read(profile.id), sql: "SHOW CREATE TABLE `\(escaped)`") - guard let ddl = result.rows.first?.last else { throw NSError(domain: "DBSyn.Migration", code: 3, userInfo: [NSLocalizedDescriptionKey: "No table definition returned."]) } + let result = try await NativeMySQLRunner.run(profile: profile, database: db, password: PasswordStore.read(profile.id), sql: "SHOW CREATE TABLE `\(escapedDB)`.`\(escaped)`") + guard let ddl = result.rows.first?.last, !ddl.isEmpty else { throw NSError(domain: "DBSyn.Migration", code: 3, userInfo: [NSLocalizedDescriptionKey: "No table definition returned."]) } sourceSQL = ddl; generate() - } catch { errorText = error.localizedDescription } + } catch { errorText = "Could not load \(db).\(tableName): \(error.localizedDescription)" } isLoading = false } }