Skip to content

Commit 22d9b1a

Browse files
authored
refactor: split ConnectionFormView into SSH, SSL, and Advanced subviews (#536)
* refactor: split ConnectionFormView into SSH, SSL, and Advanced subviews * fix: address review — remove duplicated resolvedSSHAgentSocketPath, fix testConnection SSH profile lookup
1 parent 398b591 commit 22d9b1a

4 files changed

Lines changed: 705 additions & 575 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//
2+
// ConnectionAdvancedView.swift
3+
// TablePro
4+
//
5+
// Created by Ngo Quoc Dat on 31/3/26.
6+
//
7+
8+
import SwiftUI
9+
import TableProPluginKit
10+
11+
struct ConnectionAdvancedView: View {
12+
@Binding var additionalFieldValues: [String: String]
13+
@Binding var startupCommands: String
14+
@Binding var preConnectScript: String
15+
@Binding var aiPolicy: AIConnectionPolicy?
16+
17+
let databaseType: DatabaseType
18+
let additionalConnectionFields: [ConnectionField]
19+
20+
var body: some View {
21+
Form {
22+
let advancedFields = additionalConnectionFields.filter { $0.section == .advanced }
23+
if !advancedFields.isEmpty {
24+
Section(databaseType.displayName) {
25+
ForEach(advancedFields, id: \.id) { field in
26+
if isFieldVisible(field) {
27+
ConnectionFieldRow(
28+
field: field,
29+
value: Binding(
30+
get: {
31+
additionalFieldValues[field.id]
32+
?? field.defaultValue ?? ""
33+
},
34+
set: { additionalFieldValues[field.id] = $0 }
35+
)
36+
)
37+
}
38+
}
39+
}
40+
}
41+
42+
Section(String(localized: "Startup Commands")) {
43+
StartupCommandsEditor(text: $startupCommands)
44+
.frame(height: 80)
45+
.background(Color(nsColor: .textBackgroundColor))
46+
.clipShape(RoundedRectangle(cornerRadius: 5))
47+
.overlay(
48+
RoundedRectangle(cornerRadius: 5)
49+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
50+
)
51+
Text(
52+
"SQL commands to run after connecting, e.g. SET time_zone = 'Asia/Ho_Chi_Minh'. One per line or separated by semicolons."
53+
)
54+
.font(.caption)
55+
.foregroundStyle(.secondary)
56+
}
57+
58+
Section(String(localized: "Pre-Connect Script")) {
59+
StartupCommandsEditor(text: $preConnectScript)
60+
.frame(height: 80)
61+
.background(Color(nsColor: .textBackgroundColor))
62+
.clipShape(RoundedRectangle(cornerRadius: 5))
63+
.overlay(
64+
RoundedRectangle(cornerRadius: 5)
65+
.stroke(Color(nsColor: .separatorColor), lineWidth: 1)
66+
)
67+
Text(
68+
"Shell script to run before connecting. Non-zero exit aborts connection."
69+
)
70+
.font(.caption)
71+
.foregroundStyle(.secondary)
72+
}
73+
74+
if AppSettingsManager.shared.ai.enabled {
75+
Section(String(localized: "AI")) {
76+
Picker(String(localized: "AI Policy"), selection: $aiPolicy) {
77+
Text(String(localized: "Use Default"))
78+
.tag(AIConnectionPolicy?.none as AIConnectionPolicy?)
79+
ForEach(AIConnectionPolicy.allCases) { policy in
80+
Text(policy.displayName)
81+
.tag(AIConnectionPolicy?.some(policy) as AIConnectionPolicy?)
82+
}
83+
}
84+
}
85+
}
86+
}
87+
.formStyle(.grouped)
88+
.scrollContentBackground(.hidden)
89+
}
90+
91+
private func isFieldVisible(_ field: ConnectionField) -> Bool {
92+
guard let rule = field.visibleWhen else { return true }
93+
let currentValue = additionalFieldValues[rule.fieldId] ?? defaultFieldValue(rule.fieldId)
94+
return rule.values.contains(currentValue)
95+
}
96+
97+
private func defaultFieldValue(_ fieldId: String) -> String {
98+
additionalConnectionFields.first { $0.id == fieldId }?.defaultValue ?? ""
99+
}
100+
}
101+
102+
// MARK: - Startup Commands Editor
103+
104+
struct StartupCommandsEditor: NSViewRepresentable {
105+
@Binding var text: String
106+
107+
func makeNSView(context: Context) -> NSScrollView {
108+
let scrollView = NSTextView.scrollableTextView()
109+
guard let textView = scrollView.documentView as? NSTextView else { return scrollView }
110+
111+
textView.font = .monospacedSystemFont(ofSize: NSFont.systemFontSize, weight: .regular)
112+
textView.isAutomaticQuoteSubstitutionEnabled = false
113+
textView.isAutomaticDashSubstitutionEnabled = false
114+
textView.isAutomaticTextReplacementEnabled = false
115+
textView.isAutomaticSpellingCorrectionEnabled = false
116+
textView.isRichText = false
117+
textView.string = text
118+
textView.textContainerInset = NSSize(width: 2, height: 6)
119+
textView.drawsBackground = false
120+
textView.delegate = context.coordinator
121+
122+
scrollView.borderType = .noBorder
123+
scrollView.hasVerticalScroller = true
124+
scrollView.drawsBackground = false
125+
126+
return scrollView
127+
}
128+
129+
func updateNSView(_ scrollView: NSScrollView, context: Context) {
130+
guard let textView = scrollView.documentView as? NSTextView else { return }
131+
if textView.string != text {
132+
textView.string = text
133+
}
134+
}
135+
136+
func makeCoordinator() -> Coordinator {
137+
Coordinator(text: $text)
138+
}
139+
140+
final class Coordinator: NSObject, NSTextViewDelegate {
141+
private var text: Binding<String>
142+
143+
init(text: Binding<String>) {
144+
self.text = text
145+
}
146+
147+
func textDidChange(_ notification: Notification) {
148+
guard let textView = notification.object as? NSTextView else { return }
149+
text.wrappedValue = textView.string
150+
}
151+
}
152+
}

0 commit comments

Comments
 (0)