Skip to content

Commit 5442f2f

Browse files
committed
Refactor ProfileManager to use a shared instance and update README with connection instructions. Enhance ProfileListView with double-click functionality for profile connections and add group renaming feature in SidebarView.
1 parent b072553 commit 5442f2f

8 files changed

Lines changed: 88 additions & 48 deletions

File tree

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,12 @@ iTermGUI/
119119
- **Groups**: Profiles are automatically grouped (All, Favorites, Recent)
120120

121121
#### Connecting to Servers
122-
- **Single Connection**: Double-click a profile or select and press Enter
122+
- **Single Connection**: Select a profile and press Enter (or use the Connect button)
123123
- **Multiple Connections**:
124124
- Use Cmd+Click to select multiple profiles
125-
- Choose "Connect in Tabs" or "Connect in Windows" from the toolbar
125+
- Press Enter or choose "Connect in Tabs" or "Connect in Windows" from the toolbar
126126
- **Quick Connect**: Use the context menu (right-click) for quick actions
127+
- **Menu Bar**: Click the terminal icon in the menu bar for quick access to favorite and recent profiles
127128

128129
### Advanced Features
129130

Sources/iTermGUI/App/AppDelegate.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,13 @@ class AppDelegate: NSObject, NSApplicationDelegate {
3737
}
3838
}
3939

40-
@objc func togglePopover() {
40+
@MainActor @objc func togglePopover() {
4141
if let button = statusBarItem?.button {
4242
if popover == nil {
4343
let popover = NSPopover()
4444
popover.contentSize = NSSize(width: 300, height: 400)
4545
popover.behavior = .transient
46-
popover.contentViewController = NSHostingController(rootView: QuickConnectView())
46+
popover.contentViewController = NSHostingController(rootView: QuickConnectView().environmentObject(ProfileManager.shared))
4747
self.popover = popover
4848
}
4949

Sources/iTermGUI/App/iTermGUIApp.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import SwiftUI
22

33
@main
44
struct iTermGUIApp: App {
5-
@StateObject private var profileManager = ProfileManager()
5+
@StateObject private var profileManager = ProfileManager.shared
66
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
77

88
var body: some Scene {
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import SwiftUI
2+
import AppKit
3+
4+
extension View {
5+
func onDoubleClick(perform action: @escaping () -> Void) -> some View {
6+
self.overlay(
7+
DoubleClickHandler(action: action)
8+
)
9+
}
10+
}
11+
12+
struct DoubleClickHandler: NSViewRepresentable {
13+
let action: () -> Void
14+
15+
func makeNSView(context: Context) -> NSView {
16+
let view = DoubleClickView()
17+
view.action = action
18+
return view
19+
}
20+
21+
func updateNSView(_ nsView: NSView, context: Context) {}
22+
}
23+
24+
class DoubleClickView: NSView {
25+
var action: (() -> Void)?
26+
27+
override func mouseDown(with event: NSEvent) {
28+
super.mouseDown(with: event)
29+
30+
if event.clickCount == 2 {
31+
action?()
32+
}
33+
}
34+
35+
override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
36+
return true
37+
}
38+
}

Sources/iTermGUI/Models/SSHProfile.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ struct SSHProfile: Identifiable, Codable, Hashable {
1010
var authMethod: AuthMethod
1111
var privateKeyPath: String?
1212
var password: String?
13-
var group: ProfileGroup?
1413
var tags: Set<String>
1514
var jumpHost: String?
1615
var localForwards: [PortForward]
@@ -37,7 +36,6 @@ struct SSHProfile: Identifiable, Codable, Hashable {
3736
authMethod: AuthMethod = .publicKey,
3837
privateKeyPath: String? = nil,
3938
password: String? = nil,
40-
group: ProfileGroup? = nil,
4139
tags: Set<String> = [],
4240
jumpHost: String? = nil,
4341
localForwards: [PortForward] = [],
@@ -63,7 +61,6 @@ struct SSHProfile: Identifiable, Codable, Hashable {
6361
self.authMethod = authMethod
6462
self.privateKeyPath = privateKeyPath
6563
self.password = password
66-
self.group = group
6764
self.tags = tags
6865
self.jumpHost = jumpHost
6966
self.localForwards = localForwards

Sources/iTermGUI/ViewModels/ProfileManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum ProfileSortOption: String, CaseIterable {
2727

2828
@MainActor
2929
class ProfileManager: ObservableObject {
30+
static let shared = ProfileManager()
3031
@Published var profiles: [SSHProfile] = []
3132
@Published var groups: [ProfileGroup] = ProfileGroup.defaultGroups
3233
@Published var selectedProfile: SSHProfile?
@@ -173,7 +174,6 @@ class ProfileManager: ObservableObject {
173174
authMethod: profile.authMethod,
174175
privateKeyPath: profile.privateKeyPath,
175176
password: profile.password,
176-
group: profile.group,
177177
tags: profile.tags,
178178
jumpHost: profile.jumpHost,
179179
localForwards: profile.localForwards,
@@ -205,7 +205,7 @@ class ProfileManager: ObservableObject {
205205
if let index = profiles.firstIndex(where: { $0.id == profile.id }) {
206206
profiles[index].lastUsed = Date()
207207
}
208-
iTerm2Service.openConnection(profile: profile, mode: .windows)
208+
iTerm2Service.openConnection(profile: profile, mode: connectionMode)
209209
}
210210

211211
func connectToProfiles(_ profiles: [SSHProfile], mode: ConnectionMode = .tabs) {

Sources/iTermGUI/Views/ProfileListView.swift

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import SwiftUI
2-
import AppKit
32

43
struct ProfileListView: View {
54
@EnvironmentObject var profileManager: ProfileManager
6-
@State private var doubleClickMonitor: Any?
75

86
var body: some View {
97
VStack(spacing: 0) {
@@ -18,17 +16,10 @@ struct ProfileListView: View {
1816
.padding(.vertical, 8)
1917

2018
List(profileManager.filteredProfiles, selection: $profileManager.selectedProfiles) { profile in
21-
ProfileRow(profile: profile)
19+
ProfileRow(profile: profile, onDoubleClick: {
20+
profileManager.connectToProfile(profile)
21+
})
2222
.tag(profile)
23-
.background(
24-
Color.clear
25-
.onAppear {
26-
// Set up double-click monitoring when a row appears
27-
if doubleClickMonitor == nil {
28-
setupDoubleClickMonitor()
29-
}
30-
}
31-
)
3223
.contextMenu {
3324
if profileManager.selectedProfiles.contains(profile) && profileManager.selectedProfiles.count > 1 {
3425
// Multi-selection context menu
@@ -158,36 +149,12 @@ struct ProfileListView: View {
158149
}
159150
}
160151
}
161-
.onDisappear {
162-
// Clean up the event monitor
163-
if let monitor = doubleClickMonitor {
164-
NSEvent.removeMonitor(monitor)
165-
doubleClickMonitor = nil
166-
}
167-
}
168-
}
169-
170-
private func setupDoubleClickMonitor() {
171-
doubleClickMonitor = NSEvent.addLocalMonitorForEvents(matching: .leftMouseDown) { event in
172-
if event.clickCount == 2 {
173-
// Double-click detected
174-
if let firstProfile = profileManager.selectedProfiles.first {
175-
DispatchQueue.main.async {
176-
if profileManager.selectedProfiles.count == 1 {
177-
profileManager.connectToProfile(firstProfile)
178-
} else {
179-
profileManager.connectToProfiles(Array(profileManager.selectedProfiles), mode: .tabs)
180-
}
181-
}
182-
}
183-
}
184-
return event
185-
}
186152
}
187153
}
188154

189155
struct ProfileRow: View {
190156
let profile: SSHProfile
157+
var onDoubleClick: (() -> Void)? = nil
191158

192159
var body: some View {
193160
HStack {
@@ -213,6 +180,10 @@ struct ProfileRow: View {
213180
Spacer()
214181
}
215182
.padding(.vertical, 4)
183+
.contentShape(Rectangle())
184+
.onDoubleClick {
185+
onDoubleClick?()
186+
}
216187
}
217188
}
218189

Sources/iTermGUI/Views/SidebarView.swift

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ struct SidebarView: View {
44
@EnvironmentObject var profileManager: ProfileManager
55
@State private var isAddingGroup = false
66
@State private var newGroupName = ""
7+
@State private var renamingGroup: ProfileGroup?
8+
@State private var renameText = ""
79

810
var body: some View {
911
List(selection: $profileManager.selectedGroup) {
@@ -27,7 +29,8 @@ struct SidebarView: View {
2729
.contextMenu {
2830
if !["All Profiles", "Favorites", "Recent"].contains(group.name) {
2931
Button("Rename") {
30-
// TODO: Implement rename
32+
renameText = group.name
33+
renamingGroup = group
3134
}
3235
Button("Delete", role: .destructive) {
3336
profileManager.groups.removeAll { $0.id == group.id }
@@ -84,6 +87,36 @@ struct SidebarView: View {
8487
profileManager.selectedGroup = profileManager.groups.first
8588
}
8689
}
90+
.sheet(item: $renamingGroup) { group in
91+
VStack(spacing: 15) {
92+
Text("Rename Group")
93+
.font(.headline)
94+
TextField("Group Name", text: $renameText)
95+
.textFieldStyle(.roundedBorder)
96+
.frame(width: 200)
97+
HStack {
98+
Button("Cancel") {
99+
renamingGroup = nil
100+
renameText = ""
101+
}
102+
.keyboardShortcut(.escape)
103+
104+
Button("Rename") {
105+
if !renameText.isEmpty,
106+
let index = profileManager.groups.firstIndex(where: { $0.id == group.id }) {
107+
profileManager.groups[index].name = renameText
108+
profileManager.saveProfiles()
109+
}
110+
renamingGroup = nil
111+
renameText = ""
112+
}
113+
.keyboardShortcut(.return)
114+
.buttonStyle(.borderedProminent)
115+
}
116+
}
117+
.padding()
118+
.frame(width: 300, height: 150)
119+
}
87120
}
88121
}
89122

0 commit comments

Comments
 (0)