Skip to content
Merged

wip #10

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
Binary file not shown.
5 changes: 5 additions & 0 deletions OpenTable/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ struct ContentView: View {
escapeKeyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
// Escape key code is 53
if event.keyCode == 53 {
// Don't consume ESC if a sheet is presented - let it dismiss the sheet
if AppState.shared.isSheetPresented {
Comment thread
datlechin marked this conversation as resolved.
return event
}

NotificationCenter.default.post(name: .clearSelection, object: nil)
// Return nil to consume the event, or return event to let it propagate
return nil
Expand Down
3 changes: 3 additions & 0 deletions OpenTable/Core/Database/DatabaseDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ protocol DatabaseDriver: AnyObject {

/// Fetch table metadata (size, comment, engine, etc.)
func fetchTableMetadata(tableName: String) async throws -> TableMetadata

/// Fetch list of all databases on the server
func fetchDatabases() async throws -> [String]
}

/// Default implementation for common operations
Expand Down
6 changes: 6 additions & 0 deletions OpenTable/Core/Database/MySQLDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -448,4 +448,10 @@ final class MySQLDriver: DatabaseDriver {

return result.trimmingCharacters(in: .whitespacesAndNewlines)
}

/// Fetch list of all databases on the server
func fetchDatabases() async throws -> [String] {
let result = try await execute(query: "SHOW DATABASES")
return result.rows.compactMap { row in row.first.flatMap { $0 } }
}
}
6 changes: 6 additions & 0 deletions OpenTable/Core/Database/PostgreSQLDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -385,4 +385,10 @@ final class PostgreSQLDriver: DatabaseDriver {

return result.trimmingCharacters(in: .whitespacesAndNewlines)
}

/// Fetch list of all databases on the server
func fetchDatabases() async throws -> [String] {
let result = try await execute(query: "SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY datname")
return result.rows.compactMap { row in row.first.flatMap { $0 } }
}
}
9 changes: 8 additions & 1 deletion OpenTable/Core/Database/SQLiteDriver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -347,11 +347,18 @@ final class SQLiteDriver: DatabaseDriver {
}

// MARK: - Helpers

private func expandPath(_ path: String) -> String {
if path.hasPrefix("~") {
return NSString(string: path).expandingTildeInPath
}
return path
}

/// SQLite databases are file-based, so this returns an empty array
func fetchDatabases() async throws -> [String] {
// SQLite doesn't have a concept of multiple databases on a server
// Each SQLite file is a separate database
return []
}
}
22 changes: 22 additions & 0 deletions OpenTable/Extensions/FocusedValues+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// FocusedValues+Extensions.swift
// OpenTable
//

import SwiftUI

// MARK: - Database Switcher Focus

/// Key for tracking whether DatabaseSwitcher sheet is currently open
struct IsDatabaseSwitcherOpenKey: FocusedValueKey {
typealias Value = Bool
}

extension FocusedValues {
/// Whether the DatabaseSwitcher sheet is currently presented
/// Used by commands to disable conflicting keyboard shortcuts
var isDatabaseSwitcherOpen: Bool? {
get { self[IsDatabaseSwitcherOpenKey.self] }
set { self[IsDatabaseSwitcherOpenKey.self] = newValue }
}
}
5 changes: 5 additions & 0 deletions OpenTable/Models/ConnectionToolbarState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ final class ConnectionToolbarState: ObservableObject {
/// Connection name for display
@Published var connectionName: String = ""

/// Current database name
@Published var databaseName: String = ""

/// Custom display color for the connection (uses database type color if not set)
@Published var displayColor: Color = .orange

Expand Down Expand Up @@ -188,6 +191,7 @@ final class ConnectionToolbarState: ObservableObject {
/// Update state from a DatabaseConnection model
func update(from connection: DatabaseConnection) {
connectionName = connection.name
databaseName = connection.database
databaseType = connection.type
displayColor = connection.displayColor
tagId = connection.tagId
Expand All @@ -213,6 +217,7 @@ final class ConnectionToolbarState: ObservableObject {
databaseType = .mysql
databaseVersion = nil
connectionName = ""
databaseName = ""
displayColor = databaseType.themeColor
connectionState = .disconnected
isExecuting = false
Expand Down
153 changes: 88 additions & 65 deletions OpenTable/OpenTableApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,81 @@ final class AppState: ObservableObject {
@Published var hasRowSelection: Bool = false // True when rows are selected in data grid
@Published var hasTableSelection: Bool = false // True when tables are selected in sidebar
@Published var isHistoryPanelVisible: Bool = false // Global history panel visibility
@Published var isSheetPresented: Bool = false // True when any modal sheet is open (blocks ESC key handling)
}

// MARK: - Pasteboard Commands with FocusedValue Support

/// Custom Commands struct to properly access FocusedValue for disabling ESC when sheet is open
struct PasteboardCommands: Commands {
@ObservedObject var appState: AppState
@FocusedValue(\.isDatabaseSwitcherOpen) var isDatabaseSwitcherOpen: Bool?

var body: some Commands {
CommandGroup(replacing: .pasteboard) {
Button("Cut") {
NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil)
}
.keyboardShortcut("x", modifiers: .command)

Button("Copy") {
// Check if user is editing text in a cell (firstResponder is NSTextView field editor)
if let firstResponder = NSApp.keyWindow?.firstResponder,
firstResponder is NSTextView {
// User is editing text - let standard copy handle selected text
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
} else if appState.hasRowSelection {
// Copy entire rows when rows are selected
NotificationCenter.default.post(name: .copySelectedRows, object: nil)
} else if appState.hasTableSelection {
// Copy table names when tables are selected
NotificationCenter.default.post(name: .copyTableNames, object: nil)
} else {
// Fallback to standard copy
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
}
}
.keyboardShortcut("c", modifiers: .command)

Button("Paste") {
NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil)
}
.keyboardShortcut("v", modifiers: .command)

Button("Delete") {
// Check if first responder is the history panel's table view
// History panel uses responder chain for delete actions
// Data grid uses notifications for batched undo support
if let firstResponder = NSApp.keyWindow?.firstResponder {
// Check class name to identify HistoryTableView
let className = String(describing: type(of: firstResponder))
if className.contains("HistoryTableView") {
// Let history panel handle via responder chain
NSApp.sendAction(#selector(NSText.delete(_:)), to: nil, from: nil)
return
}
}

// For data grid and other views, use notification for batched undo
NotificationCenter.default.post(name: .deleteSelectedRows, object: nil)
}
.keyboardShortcut(.delete, modifiers: .command)
.disabled(!appState.isCurrentTabEditable && !appState.hasTableSelection)

Divider()

Button("Select All") {
NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil)
}
.keyboardShortcut("a", modifiers: .command)

Button("Clear Selection") {
NotificationCenter.default.post(name: .clearSelection, object: nil)
}
.keyboardShortcut(.escape, modifiers: [])
.disabled(isDatabaseSwitcherOpen == true)
}
}
}

// MARK: - App
Expand Down Expand Up @@ -69,6 +144,12 @@ struct OpenTableApp: App {
.keyboardShortcut("t", modifiers: .command)
.disabled(!appState.isConnected)

Button("Open Database...") {
NotificationCenter.default.post(name: .openDatabaseSwitcher, object: nil)
}
.keyboardShortcut("k", modifiers: .command)
.disabled(!appState.isConnected)

Divider()

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

// Edit menu - replace pasteboard to add our Delete with shortcut
CommandGroup(replacing: .pasteboard) {
Button("Cut") {
NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: nil)
}
.keyboardShortcut("x", modifiers: .command)

Button("Copy") {
// Check if user is editing text in a cell (firstResponder is NSTextView field editor)
if let firstResponder = NSApp.keyWindow?.firstResponder,
firstResponder is NSTextView {
// User is editing text - let standard copy handle selected text
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
} else if appState.hasRowSelection {
// Copy entire rows when rows are selected
NotificationCenter.default.post(name: .copySelectedRows, object: nil)
} else if appState.hasTableSelection {
// Copy table names when tables are selected
NotificationCenter.default.post(name: .copyTableNames, object: nil)
} else {
// Fallback to standard copy
NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: nil)
}
}
.keyboardShortcut("c", modifiers: .command)

Button("Paste") {
NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: nil)
}
.keyboardShortcut("v", modifiers: .command)

Button("Delete") {
// Check if first responder is the history panel's table view
// History panel uses responder chain for delete actions
// Data grid uses notifications for batched undo support
if let firstResponder = NSApp.keyWindow?.firstResponder {
// Check class name to identify HistoryTableView
let className = String(describing: type(of: firstResponder))
if className.contains("HistoryTableView") {
// Let history panel handle via responder chain
NSApp.sendAction(#selector(NSText.delete(_:)), to: nil, from: nil)
return
}
}

// For data grid and other views, use notification for batched undo
NotificationCenter.default.post(name: .deleteSelectedRows, object: nil)
}
.keyboardShortcut(.delete, modifiers: .command)
.disabled(!appState.isCurrentTabEditable && !appState.hasTableSelection)

Divider()

Button("Select All") {
NSApp.sendAction(#selector(NSText.selectAll(_:)), to: nil, from: nil)
}
.keyboardShortcut("a", modifiers: .command)

Button("Clear Selection") {
NotificationCenter.default.post(name: .clearSelection, object: nil)
}
.keyboardShortcut(.escape, modifiers: [])
}

// Edit menu - pasteboard commands with FocusedValue support
PasteboardCommands(appState: appState)

// Edit menu - row operations (after pasteboard)
CommandGroup(after: .pasteboard) {
Divider()
Expand Down Expand Up @@ -286,7 +306,10 @@ extension Notification.Name {

// History panel notifications
static let toggleHistoryPanel = Notification.Name("toggleHistoryPanel")


// Database switcher notifications
static let openDatabaseSwitcher = Notification.Name("openDatabaseSwitcher")

// Window lifecycle notifications
static let mainWindowWillClose = Notification.Name("mainWindowWillClose")
}
Expand Down
2 changes: 1 addition & 1 deletion OpenTable/Views/Connection/ConnectionSidebarHeader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ struct ConnectionSidebarHeader: View {
Image(systemName: session.connection.type.iconName)
.foregroundStyle(session.connection.displayColor)

Text(session.connection.name)
Text(session.connection.database)
Comment thread
datlechin marked this conversation as resolved.
Comment thread
datlechin marked this conversation as resolved.

Spacer()

Expand Down
Loading