Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- The New Connection window now comes to the front on the first try instead of opening behind the Welcome window. This applies to Import from URL, creating a connection from a project folder, picking a database type, File > New Connection, and duplicating a connection.
- Importing a connection URL while a New Connection window was already open no longer throws the pasted URL away. Each import now opens its own window instead of re-using the one already on screen.
- Clicking a foreign key arrow in a query tab's results no longer replaces that tab and loses the query and its results. The referenced table opens in its own tab, and clicking the same reference again returns to that tab instead of opening a duplicate. A tab with unsaved cell edits is kept the same way.
- A foreign key jump between table tabs now keeps the filters you saved for the table you left and applies the hidden columns you saved for the table you land on.
- Saving a table structure change with more than one connection open no longer applies the change to a different connection or jumps the view back to it. The save now runs against the connection, database, and schema the edited table belongs to, and stops with an error instead of writing if it cannot reach them. (#2015)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,6 @@ internal final class AppLaunchCoordinator {
return raw == SceneId.welcome || raw.hasPrefix("\(SceneId.welcome)-")
}

internal static func isConnectionFormWindow(_ window: NSWindow) -> Bool {
guard let raw = window.identifier?.rawValue else { return false }
return raw == SceneId.connectionForm || raw.hasPrefix("\(SceneId.connectionForm)-")
}

private func showWelcomeWindow() {
WindowOpener.shared.openWelcome()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// ConnectionFormDraftStore.swift
// TablePro
//

import Foundation

internal struct ConnectionFormDraft {
internal let type: DatabaseType?
internal let parsedURL: ParsedConnectionURL?

internal init(type: DatabaseType? = nil, parsedURL: ParsedConnectionURL? = nil) {
self.type = type
self.parsedURL = parsedURL
}
}

@MainActor
internal final class ConnectionFormDraftStore {
internal static let shared = ConnectionFormDraftStore()

private var drafts: [UUID: ConnectionFormDraft] = [:]

private init() {}

internal func stage(_ draft: ConnectionFormDraft) -> UUID {
let draftId = UUID()
drafts[draftId] = draft
return draftId
}

internal func consume(_ draftId: UUID) -> ConnectionFormDraft? {
defer { drafts[draftId] = nil }
return drafts[draftId]
}
}

This file was deleted.

This file was deleted.

38 changes: 24 additions & 14 deletions TablePro/Core/Services/Infrastructure/WindowOpener.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ internal final class WindowOpener {
private static let logger = Logger(subsystem: "com.TablePro", category: "WindowOpener")

@ObservationIgnored private var openWelcomeAction: (() -> Void)?
@ObservationIgnored private var openConnectionFormAction: ((UUID?) -> Void)?
@ObservationIgnored private var openConnectionFormAction: ((ConnectionFormRequest) -> Void)?
@ObservationIgnored private var openIntegrationsActivityAction: (() -> Void)?
@ObservationIgnored private var openSettingsAction: (() -> Void)?
@ObservationIgnored private var stagedDraftId: UUID?
@ObservationIgnored private var pendingCalls: [() -> Void] = []
@ObservationIgnored private var isWired = false

Expand Down Expand Up @@ -46,24 +47,33 @@ internal final class WindowOpener {
}
}

internal func openConnectionForm(editing connectionId: UUID? = nil) {
guard connectionId == nil else {
run { $0.openConnectionFormAction?(connectionId) }
return
}
internal func openConnectionForm(editing connectionId: UUID) {
run { $0.openConnectionFormAction?(.edit(connectionId: connectionId)) }
}

internal func openConnectionForm() {
presentTypeChooser(initialType: nil) { selected in
WindowOpener.shared.openConnectionForm(editing: nil, withType: selected)
WindowOpener.shared.stageConnectionFormDraft(type: selected)
}
}

internal func openConnectionForm(editing connectionId: UUID?, withType type: DatabaseType) {
PendingNewConnectionType.shared.set(type)
run { $0.openConnectionFormAction?(connectionId) }
internal func stageConnectionFormDraft(type: DatabaseType? = nil, parsedURL: ParsedConnectionURL? = nil) {
discardStagedDraft()
stagedDraftId = ConnectionFormDraftStore.shared.stage(
ConnectionFormDraft(type: type, parsedURL: parsedURL)
)
}

internal func openStagedConnectionForm() {
guard let draftId = stagedDraftId else { return }
stagedDraftId = nil
run { $0.openConnectionFormAction?(.create(draftId: draftId)) }
}

internal func openConnectionFormFromURL(_ parsed: ParsedConnectionURL) {
PendingNewConnectionImport.shared.set(parsed)
run { $0.openConnectionFormAction?(nil) }
private func discardStagedDraft() {
guard let draftId = stagedDraftId else { return }
stagedDraftId = nil
_ = ConnectionFormDraftStore.shared.consume(draftId)
}

internal func presentTypeChooser(
Expand All @@ -80,7 +90,7 @@ internal final class WindowOpener {

internal func wire(
openWelcome: @escaping () -> Void,
openConnectionForm: @escaping (UUID?) -> Void,
openConnectionForm: @escaping (ConnectionFormRequest) -> Void,
openIntegrationsActivity: @escaping () -> Void,
openSettings: @escaping () -> Void
) {
Expand Down
21 changes: 21 additions & 0 deletions TablePro/Models/Connection/ConnectionFormRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//
// ConnectionFormRequest.swift
// TablePro
//

import Foundation

internal enum ConnectionFormRequest: Codable, Hashable {
case edit(connectionId: UUID)
case create(draftId: UUID)

internal var editedConnectionId: UUID? {
guard case .edit(let connectionId) = self else { return nil }
return connectionId
}

internal var draftId: UUID? {
guard case .create(let draftId) = self else { return nil }
return draftId
}
}
5 changes: 3 additions & 2 deletions TablePro/TableProApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -927,10 +927,11 @@ struct TableProApp: App {
.windowStyle(.hiddenTitleBar)
.commandsRemoved()

WindowGroup("New Connection", id: SceneId.connectionForm, for: UUID?.self) { $editingId in
ConnectionFormView(connectionId: editingId ?? nil)
WindowGroup("New Connection", id: SceneId.connectionForm, for: ConnectionFormRequest.self) { $request in
ConnectionFormView(request: request)
.background(WindowOpenerBridge())
.background(WindowChromeConfigurator(restorable: false))
.background(WindowSelfRaiser())
.environment(\.appServices, .live)
}
.windowResizability(.contentMinSize)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ extension WelcomeViewModel {
guard let payload = pendingInstallPayload else { return }
pendingInstallPayload = nil
applySelectedDatabaseType(type, payload: payload)
WindowOpener.shared.openStagedConnectionForm()
}

func presentURLImport() {
Expand All @@ -46,7 +47,6 @@ extension WelcomeViewModel {
}

private func applySelectedDatabaseType(_ type: DatabaseType, payload: DatabaseTypeChooserPayload) {
PendingNewConnectionType.shared.set(type)
payload.onSelected(type)
}
}
6 changes: 0 additions & 6 deletions TablePro/ViewModels/WelcomeViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -668,12 +668,6 @@ final class WelcomeViewModel {
rebuildTree()
}

func focusConnectionFormWindow() {
if let window = NSApp.windows.first(where: { AppLaunchCoordinator.isConnectionFormWindow($0) }) {
window.makeKeyAndOrderFront(nil)
}
}

// MARK: - Private Helpers

private func handleConnectError(_ error: Error, connection: DatabaseConnection) {
Expand Down
1 change: 0 additions & 1 deletion TablePro/Views/Connection/WelcomeContextMenus.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ extension WelcomeWindowView {

Button {
WindowOpener.shared.openConnectionForm(editing: connection.id)
vm.focusConnectionFormWindow()
} label: {
Label(String(localized: "Edit"), systemImage: "pencil")
}
Expand Down
13 changes: 9 additions & 4 deletions TablePro/Views/Connection/WelcomeWindowView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct WelcomeWindowView: View {
vm.pendingImportResultCount = nil
}
focus = .connectionList
WindowOpener.shared.openStagedConnectionForm()
}) { sheet in
switch sheet {
case .newGroup(let parentId):
Expand All @@ -110,7 +111,7 @@ struct WelcomeWindowView: View {
rootURL: url,
onSelect: { parsed in
vm.activeSheet = nil
WindowOpener.shared.openConnectionFormFromURL(parsed)
WindowOpener.shared.stageConnectionFormDraft(parsedURL: parsed)
},
onChooseAnotherFolder: {
vm.activeSheet = nil
Expand Down Expand Up @@ -702,7 +703,9 @@ private struct ConnectionCreationOverlays: ViewModifier {

func body(content: Content) -> some View {
content
.sheet(item: $vm.databaseTypeChooser) { payload in
.sheet(item: $vm.databaseTypeChooser, onDismiss: {
WindowOpener.shared.openStagedConnectionForm()
}) { payload in
DatabaseTypeChooserSheet(
initialType: payload.initialType,
onSelected: { type in
Expand All @@ -712,11 +715,13 @@ private struct ConnectionCreationOverlays: ViewModifier {
onCancel: { vm.databaseTypeChooser = nil }
)
}
.sheet(isPresented: $vm.urlImportPresented) {
.sheet(isPresented: $vm.urlImportPresented, onDismiss: {
WindowOpener.shared.openStagedConnectionForm()
}) {
ImportFromURLSheet(
onImported: { parsed in
vm.urlImportPresented = false
WindowOpener.shared.openConnectionFormFromURL(parsed)
WindowOpener.shared.stageConnectionFormDraft(parsedURL: parsed)
},
onCancel: {
vm.urlImportPresented = false
Expand Down
22 changes: 11 additions & 11 deletions TablePro/Views/ConnectionForm/ConnectionFormView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import SwiftUI
import TableProPluginKit

struct ConnectionFormView: View {
let connectionId: UUID?
let request: ConnectionFormRequest?

@State private var coordinator: ConnectionFormCoordinator?
@Environment(\.dismiss) private var dismiss
Expand All @@ -21,25 +21,25 @@ struct ConnectionFormView: View {
.frame(minWidth: 720, minHeight: 560)
}
}
.task(id: connectionId) {
.task(id: request) {
guard coordinator == nil else { return }
let pendingImport = connectionId == nil
? PendingNewConnectionImport.shared.consume()
: nil
let pendingType = connectionId == nil
? PendingNewConnectionType.shared.consume()
: nil
let draft = consumeDraft()
let new = ConnectionFormCoordinator(
connectionId: connectionId,
initialType: pendingType,
initialParsedURL: pendingImport
connectionId: request?.editedConnectionId,
initialType: draft?.type,
initialParsedURL: draft?.parsedURL
)
new.dismissAction = { dismiss() }
new.start()
new.detectClipboardConnectionStringIfNeeded()
coordinator = new
}
}

private func consumeDraft() -> ConnectionFormDraft? {
guard let draftId = request?.draftId else { return nil }
return ConnectionFormDraftStore.shared.consume(draftId)
}
}

private struct ConnectionFormContent: View {
Expand Down
2 changes: 1 addition & 1 deletion TablePro/Views/Infrastructure/WindowOpenerBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ internal struct WindowOpenerBridge: View {
private func wireUp() {
WindowOpener.shared.wire(
openWelcome: { openWindow(id: SceneId.welcome) },
openConnectionForm: { id in openWindow(id: SceneId.connectionForm, value: id) },
openConnectionForm: { request in openWindow(id: SceneId.connectionForm, value: request) },
openIntegrationsActivity: { openWindow(id: SceneId.integrationsActivity) },
openSettings: { openSettings() }
)
Expand Down
26 changes: 26 additions & 0 deletions TablePro/Views/Infrastructure/WindowSelfRaiser.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// WindowSelfRaiser.swift
// TablePro
//

import AppKit
import SwiftUI

internal struct WindowSelfRaiser: NSViewRepresentable {
func makeNSView(context: Context) -> NSView {
RaisingHostView()
}

func updateNSView(_ nsView: NSView, context: Context) {}
}

private final class RaisingHostView: NSView {
private var hasRaised = false

override func viewDidMoveToWindow() {
super.viewDidMoveToWindow()
guard let window, !hasRaised else { return }
hasRaised = true
window.makeKeyAndOrderFront(nil)
}
}
Loading
Loading