diff --git a/apps/macos/Sources/Clawdis/GeneralSettings.swift b/apps/macos/Sources/Clawdis/GeneralSettings.swift index 9dda12dfa..952af6143 100644 --- a/apps/macos/Sources/Clawdis/GeneralSettings.swift +++ b/apps/macos/Sources/Clawdis/GeneralSettings.swift @@ -151,7 +151,7 @@ struct GeneralSettings: View { .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) } - MasterDiscoveryInlineList(discovery: self.masterDiscovery) { master in + MasterDiscoveryInlineList(discovery: self.masterDiscovery, currentTarget: self.state.remoteTarget) { master in self.applyDiscoveredMaster(master) } .padding(.leading, 58) diff --git a/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift b/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift index 887713652..4c861fb92 100644 --- a/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift +++ b/apps/macos/Sources/Clawdis/MasterDiscoveryMenu.swift @@ -4,7 +4,9 @@ import SwiftUI // swiftlint:disable:next inclusive_language struct MasterDiscoveryInlineList: View { @ObservedObject var discovery: MasterDiscoveryModel + var currentTarget: String? var onSelect: (MasterDiscoveryModel.DiscoveredMaster) -> Void + @State private var hoveredMasterID: MasterDiscoveryModel.DiscoveredMaster.ID? var body: some View { VStack(alignment: .leading, spacing: 6) { @@ -24,22 +26,54 @@ struct MasterDiscoveryInlineList: View { } else { VStack(alignment: .leading, spacing: 6) { ForEach(self.discovery.masters.prefix(6)) { master in + let target = self.suggestedSSHTarget(master) + let selected = target != nil && self.currentTarget?.trimmingCharacters(in: .whitespacesAndNewlines) == target + Button { - self.onSelect(master) + withAnimation(.spring(response: 0.25, dampingFraction: 0.9)) { + self.onSelect(master) + } } label: { - HStack(spacing: 8) { - Text(master.displayName) - .lineLimit(1) - Spacer() - if let host = master.tailnetDns ?? master.lanHost { - Text(host) - .font(.caption2) - .foregroundStyle(.secondary) + HStack(alignment: .center, spacing: 10) { + VStack(alignment: .leading, spacing: 2) { + Text(master.displayName) + .font(.callout.weight(.semibold)) .lineLimit(1) + .truncationMode(.tail) + if let target { + Text(target) + .font(.caption.monospaced()) + .foregroundStyle(.secondary) + .lineLimit(1) + .truncationMode(.middle) + } + } + Spacer(minLength: 0) + if selected { + Image(systemName: "checkmark.circle.fill") + .foregroundStyle(Color.accentColor) + } else { + Image(systemName: "arrow.right.circle") + .foregroundStyle(.secondary) } } + .padding(.horizontal, 10) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + .background( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .fill(self.rowBackground(selected: selected, hovered: self.hoveredMasterID == master.id))) + .overlay( + RoundedRectangle(cornerRadius: 10, style: .continuous) + .strokeBorder( + selected ? Color.accentColor.opacity(0.45) : Color.clear, + lineWidth: 1)) + .contentShape(Rectangle()) } .buttonStyle(.plain) + .onHover { hovering in + self.hoveredMasterID = hovering ? master.id : (self.hoveredMasterID == master.id ? nil : self.hoveredMasterID) + } } } .padding(10) @@ -48,7 +82,24 @@ struct MasterDiscoveryInlineList: View { .fill(Color(NSColor.controlBackgroundColor))) } } - .help("Discover Clawdis masters on your LAN") + .help("Click a discovered master to fill the SSH target.") + } + + private func suggestedSSHTarget(_ master: MasterDiscoveryModel.DiscoveredMaster) -> String? { + let host = master.tailnetDns ?? master.lanHost + guard let host else { return nil } + let user = NSUserName() + var target = "\(user)@\(host)" + if master.sshPort != 22 { + target += ":\(master.sshPort)" + } + return target + } + + private func rowBackground(selected: Bool, hovered: Bool) -> Color { + if selected { return Color.accentColor.opacity(0.12) } + if hovered { return Color.secondary.opacity(0.08) } + return Color.clear } } diff --git a/apps/macos/Sources/Clawdis/Onboarding.swift b/apps/macos/Sources/Clawdis/Onboarding.swift index e38547c43..5adfff441 100644 --- a/apps/macos/Sources/Clawdis/Onboarding.swift +++ b/apps/macos/Sources/Clawdis/Onboarding.swift @@ -247,7 +247,7 @@ struct OnboardingView: View { .frame(width: fieldWidth) } - MasterDiscoveryInlineList(discovery: self.masterDiscovery) { master in + MasterDiscoveryInlineList(discovery: self.masterDiscovery, currentTarget: self.state.remoteTarget) { master in self.applyDiscoveredMaster(master) } .frame(width: fieldWidth, alignment: .leading)