Skip to content

Commit abdf28a

Browse files
authored
Merge pull request #286 from datlechin/refactor/direct-sidebar-reload
refactor: replace broadcasts with direct coordinator calls
2 parents 9ce1f10 + 7e49164 commit abdf28a

File tree

7 files changed

+150
-207
lines changed

7 files changed

+150
-207
lines changed

TablePro/ContentView.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,6 @@ struct ContentView: View {
201201
tables: sessionTablesBinding,
202202
sidebarState: SharedSidebarState.forConnection(currentSession.connection.id),
203203
activeTableName: windowTitle,
204-
onShowAllTables: {
205-
showAllTablesMetadata()
206-
},
207204
onDoubleClick: { table in
208205
let isView = table.type == .view
209206
if let preview = WindowLifecycleMonitor.shared.previewWindow(for: currentSession.connection.id),
@@ -393,10 +390,6 @@ struct ContentView: View {
393390
storage.deleteConnection(connection)
394391
}
395392

396-
private func showAllTablesMetadata() {
397-
// Post notification for MainContentView to handle
398-
NotificationCenter.default.post(name: .showAllTables, object: nil)
399-
}
400393
}
401394

402395
#Preview {

TablePro/TableProApp.swift

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ struct PasteboardCommands: Commands {
6161
case .copyRows:
6262
actions?.copySelectedRows()
6363
case .copyTableNames:
64-
NotificationCenter.default.post(name: .copyTableNames, object: nil)
64+
actions?.copyTableNames()
6565
}
6666
}
6767
.optionalKeyboardShortcut(shortcut(for: .copy))
@@ -292,7 +292,7 @@ struct AppMenuCommands: Commands {
292292

293293
// Table operations (work when tables selected in sidebar)
294294
Button("Truncate Table") {
295-
NotificationCenter.default.post(name: .truncateTables, object: nil)
295+
actions?.truncateTables()
296296
}
297297
.optionalKeyboardShortcut(shortcut(for: .truncateTable))
298298
.disabled(!appState.hasTableSelection || appState.isReadOnly)
@@ -462,26 +462,17 @@ extension Notification.Name {
462462
static let pasteRows = Notification.Name("pasteRows")
463463
static let undoChange = Notification.Name("undoChange")
464464
static let redoChange = Notification.Name("redoChange")
465-
static let clearSelection = Notification.Name("clearSelection")
466465

467466
// Tab operations
468-
static let showAllTables = Notification.Name("showAllTables")
469467
static let newQueryTab = Notification.Name("newQueryTab")
470468

471469
// Sidebar operations (still posted by SidebarView / ConnectionStatusView)
472-
static let copyTableNames = Notification.Name("copyTableNames")
473-
static let truncateTables = Notification.Name("truncateTables")
474-
static let exportTables = Notification.Name("exportTables")
475-
static let importTables = Notification.Name("importTables")
476470
static let openDatabaseSwitcher = Notification.Name("openDatabaseSwitcher")
477471

478-
// Structure view / sidebar operations (still posted by SidebarView, QueryEditorView)
479-
static let createView = Notification.Name("createView")
472+
// Structure view operations (still posted by QueryEditorView)
480473
static let explainQuery = Notification.Name("explainQuery")
481474
static let saveStructureChanges = Notification.Name("saveStructureChanges")
482475
static let previewStructureSQL = Notification.Name("previewStructureSQL")
483-
static let showTableStructure = Notification.Name("showTableStructure")
484-
static let editViewDefinition = Notification.Name("editViewDefinition")
485476

486477
// File opening notifications
487478
static let openSQLFiles = Notification.Name("openSQLFiles")

TablePro/ViewModels/SidebarViewModel.swift

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@
33
// TablePro
44
//
55
// ViewModel for SidebarView.
6-
// Handles table loading, search filtering, batch operations, and notification handling.
6+
// Handles table loading, search filtering, and batch operations.
77
//
88

9-
import Combine
109
import Observation
1110
import SwiftUI
1211

@@ -81,8 +80,6 @@ final class SidebarViewModel {
8180

8281
private let connectionId: UUID
8382
private let tableFetcher: TableFetcher
84-
private var cancellables = Set<AnyCancellable>()
85-
private var hasSetupNotifications = false
8683
private var loadTask: Task<Void, Never>?
8784

8885
// MARK: - Convenience Accessors
@@ -146,35 +143,6 @@ final class SidebarViewModel {
146143
}
147144
}
148145

149-
// MARK: - Notifications
150-
151-
func setupNotifications() {
152-
guard !hasSetupNotifications else { return }
153-
hasSetupNotifications = true
154-
155-
NotificationCenter.default.publisher(for: .copyTableNames)
156-
.receive(on: DispatchQueue.main)
157-
.sink { [weak self] _ in
158-
self?.copySelectedTableNames()
159-
}
160-
.store(in: &cancellables)
161-
162-
NotificationCenter.default.publisher(for: .truncateTables)
163-
.receive(on: DispatchQueue.main)
164-
.sink { [weak self] _ in
165-
guard let self, !self.selectedTables.isEmpty else { return }
166-
self.batchToggleTruncate()
167-
}
168-
.store(in: &cancellables)
169-
170-
NotificationCenter.default.publisher(for: .clearSelection)
171-
.receive(on: DispatchQueue.main)
172-
.sink { [weak self] _ in
173-
self?.selectedTables.removeAll()
174-
}
175-
.store(in: &cancellables)
176-
}
177-
178146
// MARK: - Table Loading
179147

180148
func loadTables() {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//
2+
// MainContentCoordinator+SidebarActions.swift
3+
// TablePro
4+
//
5+
// Sidebar context menu actions for MainContentCoordinator.
6+
//
7+
8+
import AppKit
9+
import Foundation
10+
import UniformTypeIdentifiers
11+
12+
extension MainContentCoordinator {
13+
// MARK: - View Operations
14+
15+
func createView() {
16+
guard !connection.safeModeLevel.blocksAllWrites else { return }
17+
18+
let template: String
19+
switch connection.type {
20+
case .postgresql, .redshift, .duckdb:
21+
template = "CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
22+
case .mysql, .mariadb, .clickhouse:
23+
template = "CREATE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
24+
case .sqlite:
25+
template = "CREATE VIEW IF NOT EXISTS view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
26+
case .mssql:
27+
template = "CREATE OR ALTER VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
28+
case .oracle:
29+
template = "CREATE OR REPLACE VIEW view_name AS\nSELECT column1, column2\nFROM table_name\nWHERE condition;"
30+
case .mongodb:
31+
template = "db.createView(\"view_name\", \"source_collection\", [\n {\"$match\": {}},\n {\"$project\": {\"_id\": 1}}\n])"
32+
case .redis:
33+
template = "-- Redis does not support views"
34+
}
35+
36+
let payload = EditorTabPayload(
37+
connectionId: connection.id,
38+
tabType: .query,
39+
databaseName: connection.database,
40+
initialQuery: template
41+
)
42+
WindowOpener.shared.openNativeTab(payload)
43+
}
44+
45+
func editViewDefinition(_ viewName: String) {
46+
Task { @MainActor in
47+
do {
48+
guard let driver = DatabaseManager.shared.driver(for: self.connection.id) else { return }
49+
let definition = try await driver.fetchViewDefinition(view: viewName)
50+
51+
let payload = EditorTabPayload(
52+
connectionId: connection.id,
53+
tabType: .query,
54+
initialQuery: definition
55+
)
56+
WindowOpener.shared.openNativeTab(payload)
57+
} catch {
58+
let fallbackSQL: String
59+
switch connection.type {
60+
case .postgresql, .redshift, .duckdb:
61+
fallbackSQL = "CREATE OR REPLACE VIEW \(viewName) AS\n-- Could not fetch view definition: \(error.localizedDescription)\nSELECT * FROM table_name;"
62+
case .mysql, .mariadb, .clickhouse:
63+
fallbackSQL = "ALTER VIEW \(viewName) AS\n-- Could not fetch view definition: \(error.localizedDescription)\nSELECT * FROM table_name;"
64+
case .sqlite:
65+
fallbackSQL = "-- SQLite does not support ALTER VIEW. Drop and recreate:\nDROP VIEW IF EXISTS \(viewName);\nCREATE VIEW \(viewName) AS\nSELECT * FROM table_name;"
66+
case .mssql:
67+
fallbackSQL = "CREATE OR ALTER VIEW \(viewName) AS\n-- Could not fetch view definition: \(error.localizedDescription)\nSELECT * FROM table_name;"
68+
case .oracle:
69+
fallbackSQL = "CREATE OR REPLACE VIEW \(viewName) AS\n-- Could not fetch view definition: \(error.localizedDescription)\nSELECT * FROM table_name;"
70+
case .mongodb:
71+
fallbackSQL = "db.runCommand({\"collMod\": \"\(viewName)\", \"viewOn\": \"source_collection\", \"pipeline\": [{\"$match\": {}}]})"
72+
case .redis:
73+
fallbackSQL = "-- Redis does not support views"
74+
}
75+
76+
let payload = EditorTabPayload(
77+
connectionId: connection.id,
78+
tabType: .query,
79+
initialQuery: fallbackSQL
80+
)
81+
WindowOpener.shared.openNativeTab(payload)
82+
}
83+
}
84+
}
85+
86+
// MARK: - Export/Import
87+
88+
func openExportDialog() {
89+
activeSheet = .exportDialog
90+
}
91+
92+
func openImportDialog() {
93+
guard !connection.safeModeLevel.blocksAllWrites else { return }
94+
guard connection.type != .mongodb && connection.type != .redis else {
95+
let typeName = connection.type == .mongodb ? "MongoDB" : "Redis"
96+
AlertHelper.showErrorSheet(
97+
title: String(localized: "Import Not Supported"),
98+
message: String(localized: "SQL import is not supported for \(typeName) connections."),
99+
window: nil
100+
)
101+
return
102+
}
103+
let panel = NSOpenPanel()
104+
var contentTypes: [UTType] = []
105+
if let sqlType = UTType(filenameExtension: "sql") {
106+
contentTypes.append(sqlType)
107+
}
108+
if let gzType = UTType(filenameExtension: "gz") {
109+
contentTypes.append(gzType)
110+
}
111+
if !contentTypes.isEmpty {
112+
panel.allowedContentTypes = contentTypes
113+
}
114+
panel.allowsMultipleSelection = false
115+
panel.message = "Select SQL file to import"
116+
117+
panel.begin { [weak self] response in
118+
guard response == .OK, let url = panel.url else { return }
119+
self?.importFileURL = url
120+
self?.activeSheet = .importDialog
121+
}
122+
}
123+
}

0 commit comments

Comments
 (0)