Skip to content

Commit 071c75b

Browse files
committed
wip
1 parent 322d02e commit 071c75b

14 files changed

Lines changed: 656 additions & 69 deletions

File tree

OpenTable/ContentView.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,11 @@ struct ContentView: View {
304304
escapeKeyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
305305
// Escape key code is 53
306306
if event.keyCode == 53 {
307+
// Don't consume ESC if a sheet is presented - let it dismiss the sheet
308+
if AppState.shared.isSheetPresented {
309+
return event
310+
}
311+
307312
NotificationCenter.default.post(name: .clearSelection, object: nil)
308313
// Return nil to consume the event, or return event to let it propagate
309314
return nil

OpenTable/Core/Database/DatabaseDriver.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ protocol DatabaseDriver: AnyObject {
5858

5959
/// Fetch table metadata (size, comment, engine, etc.)
6060
func fetchTableMetadata(tableName: String) async throws -> TableMetadata
61+
62+
/// Fetch list of all databases on the server
63+
func fetchDatabases() async throws -> [String]
6164
}
6265

6366
/// Default implementation for common operations

OpenTable/Core/Database/MySQLDriver.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,4 +448,12 @@ final class MySQLDriver: DatabaseDriver {
448448

449449
return result.trimmingCharacters(in: .whitespacesAndNewlines)
450450
}
451+
452+
/// Fetch list of all databases on the server
453+
func fetchDatabases() async throws -> [String] {
454+
let result = try await execute(query: "SHOW DATABASES")
455+
return result.rows.compactMap { row in
456+
row.first ?? nil
457+
}
458+
}
451459
}

OpenTable/Core/Database/PostgreSQLDriver.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,4 +385,12 @@ final class PostgreSQLDriver: DatabaseDriver {
385385

386386
return result.trimmingCharacters(in: .whitespacesAndNewlines)
387387
}
388+
389+
/// Fetch list of all databases on the server
390+
func fetchDatabases() async throws -> [String] {
391+
let result = try await execute(query: "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname")
392+
return result.rows.compactMap { row in
393+
row.first ?? nil
394+
}
395+
}
388396
}

OpenTable/Core/Database/SQLiteDriver.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,11 +347,18 @@ final class SQLiteDriver: DatabaseDriver {
347347
}
348348

349349
// MARK: - Helpers
350-
350+
351351
private func expandPath(_ path: String) -> String {
352352
if path.hasPrefix("~") {
353353
return NSString(string: path).expandingTildeInPath
354354
}
355355
return path
356356
}
357+
358+
/// SQLite databases are file-based, so this returns an empty array
359+
func fetchDatabases() async throws -> [String] {
360+
// SQLite doesn't have a concept of multiple databases on a server
361+
// Each SQLite file is a separate database
362+
return []
363+
}
357364
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// FocusedValues+Extensions.swift
3+
// OpenTable
4+
//
5+
6+
import SwiftUI
7+
8+
// MARK: - Database Switcher Focus
9+
10+
/// Key for tracking whether DatabaseSwitcher sheet is currently open
11+
struct IsDatabaseSwitcherOpenKey: FocusedValueKey {
12+
typealias Value = Bool
13+
}
14+
15+
extension FocusedValues {
16+
/// Whether the DatabaseSwitcher sheet is currently presented
17+
/// Used by commands to disable conflicting keyboard shortcuts
18+
var isDatabaseSwitcherOpen: Bool? {
19+
get { self[IsDatabaseSwitcherOpenKey.self] }
20+
set { self[IsDatabaseSwitcherOpenKey.self] = newValue }
21+
}
22+
}

OpenTable/Models/ConnectionToolbarState.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,9 @@ final class ConnectionToolbarState: ObservableObject {
111111
/// Connection name for display
112112
@Published var connectionName: String = ""
113113

114+
/// Current database name
115+
@Published var databaseName: String = ""
116+
114117
/// Custom display color for the connection (uses database type color if not set)
115118
@Published var displayColor: Color = .orange
116119

@@ -188,6 +191,7 @@ final class ConnectionToolbarState: ObservableObject {
188191
/// Update state from a DatabaseConnection model
189192
func update(from connection: DatabaseConnection) {
190193
connectionName = connection.name
194+
databaseName = connection.database
191195
databaseType = connection.type
192196
displayColor = connection.displayColor
193197
tagId = connection.tagId
@@ -213,6 +217,7 @@ final class ConnectionToolbarState: ObservableObject {
213217
databaseType = .mysql
214218
databaseVersion = nil
215219
connectionName = ""
220+
databaseName = ""
216221
displayColor = databaseType.themeColor
217222
connectionState = .disconnected
218223
isExecuting = false

OpenTable/OpenTableApp.swift

Lines changed: 88 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,81 @@ final class AppState: ObservableObject {
1717
@Published var hasRowSelection: Bool = false // True when rows are selected in data grid
1818
@Published var hasTableSelection: Bool = false // True when tables are selected in sidebar
1919
@Published var isHistoryPanelVisible: Bool = false // Global history panel visibility
20+
@Published var isSheetPresented: Bool = false // True when any modal sheet is open (blocks ESC key handling)
21+
}
22+
23+
// MARK: - Pasteboard Commands with FocusedValue Support
24+
25+
/// Custom Commands struct to properly access FocusedValue for disabling ESC when sheet is open
26+
struct PasteboardCommands: Commands {
27+
@ObservedObject var appState: AppState
28+
@FocusedValue(\.isDatabaseSwitcherOpen) var isDatabaseSwitcherOpen: Bool?
29+
30+
var body: some Commands {
31+
CommandGroup(replacing: .pasteboard) {
32+
Button("Cut") {
33+
NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil)
34+
}
35+
.keyboardShortcut("x", modifiers: .command)
36+
37+
Button("Copy") {
38+
// Check if user is editing text in a cell (firstResponder is NSTextView field editor)
39+
if let firstResponder = NSApp.keyWindow?.firstResponder,
40+
firstResponder is NSTextView {
41+
// User is editing text - let standard copy handle selected text
42+
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
43+
} else if appState.hasRowSelection {
44+
// Copy entire rows when rows are selected
45+
NotificationCenter.default.post(name: .copySelectedRows, object: nil)
46+
} else if appState.hasTableSelection {
47+
// Copy table names when tables are selected
48+
NotificationCenter.default.post(name: .copyTableNames, object: nil)
49+
} else {
50+
// Fallback to standard copy
51+
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
52+
}
53+
}
54+
.keyboardShortcut("c", modifiers: .command)
55+
56+
Button("Paste") {
57+
NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil)
58+
}
59+
.keyboardShortcut("v", modifiers: .command)
60+
61+
Button("Delete") {
62+
// Check if first responder is the history panel's table view
63+
// History panel uses responder chain for delete actions
64+
// Data grid uses notifications for batched undo support
65+
if let firstResponder = NSApp.keyWindow?.firstResponder {
66+
// Check class name to identify HistoryTableView
67+
let className = String(describing: type(of: firstResponder))
68+
if className.contains("HistoryTableView") {
69+
// Let history panel handle via responder chain
70+
NSApp.sendAction(#selector(NSText.delete(_:)), to: nil, from: nil)
71+
return
72+
}
73+
}
74+
75+
// For data grid and other views, use notification for batched undo
76+
NotificationCenter.default.post(name: .deleteSelectedRows, object: nil)
77+
}
78+
.keyboardShortcut(.delete, modifiers: .command)
79+
.disabled(!appState.isCurrentTabEditable && !appState.hasTableSelection)
80+
81+
Divider()
82+
83+
Button("Select All") {
84+
NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil)
85+
}
86+
.keyboardShortcut("a", modifiers: .command)
87+
88+
Button("Clear Selection") {
89+
NotificationCenter.default.post(name: .clearSelection, object: nil)
90+
}
91+
.keyboardShortcut(.escape, modifiers: [])
92+
.disabled(isDatabaseSwitcherOpen == true)
93+
}
94+
}
2095
}
2196

2297
// MARK: - App
@@ -69,6 +144,12 @@ struct OpenTableApp: App {
69144
.keyboardShortcut("t", modifiers: .command)
70145
.disabled(!appState.isConnected)
71146

147+
Button("Open Database...") {
148+
NotificationCenter.default.post(name: .openDatabaseSwitcher, object: nil)
149+
}
150+
.keyboardShortcut("k", modifiers: .command)
151+
.disabled(!appState.isConnected)
152+
72153
Divider()
73154

74155
Button("Save Changes") {
@@ -129,70 +210,9 @@ struct OpenTableApp: App {
129210
.keyboardShortcut("z", modifiers: [.command, .shift])
130211
}
131212

132-
// Edit menu - replace pasteboard to add our Delete with shortcut
133-
CommandGroup(replacing: .pasteboard) {
134-
Button("Cut") {
135-
NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil)
136-
}
137-
.keyboardShortcut("x", modifiers: .command)
138-
139-
Button("Copy") {
140-
// Check if user is editing text in a cell (firstResponder is NSTextView field editor)
141-
if let firstResponder = NSApp.keyWindow?.firstResponder,
142-
firstResponder is NSTextView {
143-
// User is editing text - let standard copy handle selected text
144-
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
145-
} else if appState.hasRowSelection {
146-
// Copy entire rows when rows are selected
147-
NotificationCenter.default.post(name: .copySelectedRows, object: nil)
148-
} else if appState.hasTableSelection {
149-
// Copy table names when tables are selected
150-
NotificationCenter.default.post(name: .copyTableNames, object: nil)
151-
} else {
152-
// Fallback to standard copy
153-
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
154-
}
155-
}
156-
.keyboardShortcut("c", modifiers: .command)
157-
158-
Button("Paste") {
159-
NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil)
160-
}
161-
.keyboardShortcut("v", modifiers: .command)
162-
163-
Button("Delete") {
164-
// Check if first responder is the history panel's table view
165-
// History panel uses responder chain for delete actions
166-
// Data grid uses notifications for batched undo support
167-
if let firstResponder = NSApp.keyWindow?.firstResponder {
168-
// Check class name to identify HistoryTableView
169-
let className = String(describing: type(of: firstResponder))
170-
if className.contains("HistoryTableView") {
171-
// Let history panel handle via responder chain
172-
NSApp.sendAction(#selector(NSText.delete(_:)), to: nil, from: nil)
173-
return
174-
}
175-
}
176-
177-
// For data grid and other views, use notification for batched undo
178-
NotificationCenter.default.post(name: .deleteSelectedRows, object: nil)
179-
}
180-
.keyboardShortcut(.delete, modifiers: .command)
181-
.disabled(!appState.isCurrentTabEditable && !appState.hasTableSelection)
182-
183-
Divider()
184-
185-
Button("Select All") {
186-
NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil)
187-
}
188-
.keyboardShortcut("a", modifiers: .command)
189-
190-
Button("Clear Selection") {
191-
NotificationCenter.default.post(name: .clearSelection, object: nil)
192-
}
193-
.keyboardShortcut(.escape, modifiers: [])
194-
}
195-
213+
// Edit menu - pasteboard commands with FocusedValue support
214+
PasteboardCommands(appState: appState)
215+
196216
// Edit menu - row operations (after pasteboard)
197217
CommandGroup(after: .pasteboard) {
198218
Divider()
@@ -286,7 +306,10 @@ extension Notification.Name {
286306

287307
// History panel notifications
288308
static let toggleHistoryPanel = Notification.Name("toggleHistoryPanel")
289-
309+
310+
// Database switcher notifications
311+
static let openDatabaseSwitcher = Notification.Name("openDatabaseSwitcher")
312+
290313
// Window lifecycle notifications
291314
static let mainWindowWillClose = Notification.Name("mainWindowWillClose")
292315
}

OpenTable/Views/Connection/ConnectionSidebarHeader.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ struct ConnectionSidebarHeader: View {
3737
Image(systemName: session.connection.type.iconName)
3838
.foregroundStyle(session.connection.displayColor)
3939

40-
Text(session.connection.name)
40+
Text(session.connection.database)
4141

4242
Spacer()
4343

@@ -98,7 +98,7 @@ struct ConnectionSidebarHeader: View {
9898
}
9999

100100
// Connection name
101-
Text(currentSession?.connection.name ?? "No Connection")
101+
Text(currentSession?.connection.database ?? "No Connection")
102102
.font(.system(size: 13, weight: .medium))
103103
.lineLimit(1)
104104

0 commit comments

Comments
 (0)