import SwiftUI struct MenuBarView: View { @ObservedObject var controller: DaemonController @ObservedObject var preferences: AppPreferences @Environment(\.openWindow) private var openWindow var body: some View { VStack(spacing: 0) { header.padding(.horizontal,14).padding(.top,14).padding(.bottom,10) Divider() statusGrid.padding(.horizontal,14).padding(.vertical,10) Divider() if !controller.connectedClients.isEmpty { connectionsSection.padding(.horizontal,14).padding(.vertical,8) Divider() } actionsSection.padding(.horizontal,14).padding(.vertical,8) Divider() footer.padding(.horizontal,14).padding(.vertical,8) } .frame(width: 300) } private var header: some View { HStack(spacing:10) { Circle().fill(stateColor).frame(width:10,height:10).shadow(color:stateColor.opacity(0.6),radius:4) VStack(alignment:.leading,spacing:2) { Text("PadXcode Daemon").font(.headline) Text(controller.serverState.statusLabel).font(.subheadline).foregroundStyle(.secondary) } Spacer() Button { Task { if controller.serverState.isRunning { await controller.stop() } else if case .starting = controller.serverState { } // ignore during startup else { await controller.start() } } } label: { if case .starting = controller.serverState { ProgressView().scaleEffect(0.6).frame(width:44) } else { Text(controller.serverState.isRunning ? "Stop" : "Start").frame(width:44) } } .buttonStyle(.bordered).controlSize(.small) .tint(controller.serverState.isRunning ? .red : .green) .disabled({ if case .starting = controller.serverState { return true }; return false }()) } } private var statusGrid: some View { Grid(alignment:.leading,horizontalSpacing:12,verticalSpacing:6) { GridRow { StatusCell(label:"Port", value:"\(preferences.port)", icon:"antenna.radiowaves.left.and.right") StatusCell(label:"Uptime", value:controller.serverState.uptime ?? "—", icon:"clock") } GridRow { StatusCell(label:"Tailscale", value:preferences.tailscaleIP, icon:"network") StatusCell(label:"Builds", value:"\(controller.buildCount)", icon:"hammer") } GridRow { StatusCell(label:"Clients", value:"\(controller.connectedClients.count)", icon:"ipad.and.iphone") StatusCell(label:"Config", value:preferences.developmentTeam.isEmpty ? "⚠️ No Team ID" : "✓ Signed", icon:"checkmark.seal") } } } private var connectionsSection: some View { VStack(alignment:.leading,spacing:4) { Text("Active Connections").font(.caption.bold()).foregroundStyle(.secondary) ForEach(controller.connectedClients) { c in HStack(spacing:6) { Circle().fill(.green).frame(width:6,height:6) Text(c.type.rawValue).font(.caption) Spacer() Text(c.sessionId.prefix(8)).font(.system(.caption2,design:.monospaced)).foregroundStyle(.secondary) } } } } private var actionsSection: some View { VStack(spacing:2) { MenuAction(label:"Restart Server", icon:"arrow.clockwise") { Task { await controller.restart() } } MenuAction(label:"View Log", icon:"doc.text.magnifyingglass") { openWindow(id:"log"); NSApp.activate(ignoringOtherApps:true) } MenuAction(label:"Copy Server URL", icon:"doc.on.clipboard") { let url = "http://\(preferences.tailscaleIP):\(preferences.port)" NSPasteboard.general.clearContents(); NSPasteboard.general.setString(url, forType:.string) } MenuAction(label:"Open Config Folder", icon:"folder") { NSWorkspace.shared.open(URL(fileURLWithPath:NSHomeDirectory()).appendingPathComponent(".padxcode")) } MenuAction(label:"Settings…", icon:"gearshape", shortcut:",") { openSettingsWindow() } } } private var footer: some View { HStack { Text("v1.0.0").font(.caption2).foregroundStyle(.tertiary) Spacer() Button("Quit PadXcode Daemon") { Task { await controller.stop(); NSApp.terminate(nil) } } .buttonStyle(.plain).font(.caption).foregroundStyle(.secondary) } } private var stateColor: Color { switch controller.serverState { case .running: return .green; case .starting: return .yellow case .error: return .red; case .stopped: return .secondary } } private func openSettingsWindow() { openWindow(id:"settings") DispatchQueue.main.asyncAfter(deadline:.now()+0.05) { NSApp.setActivationPolicy(.regular); NSApp.activate(ignoringOtherApps:true) NSApp.windows.first(where:{ $0.title == "PadXcode Daemon Settings" })?.makeKeyAndOrderFront(nil) } } } struct StatusCell: View { let label: String; let value: String; let icon: String var body: some View { HStack(spacing:5) { Image(systemName:icon).font(.caption).foregroundStyle(.secondary).frame(width:14) VStack(alignment:.leading,spacing:0) { Text(label).font(.caption2).foregroundStyle(.tertiary) Text(value).font(.system(.caption,design:.monospaced)).lineLimit(1) } }.frame(maxWidth:.infinity,alignment:.leading) } } struct MenuAction: View { let label: String; let icon: String; var shortcut: String? = nil; let action: () -> Void var body: some View { Button(action:action) { HStack { Image(systemName:icon).frame(width:16).foregroundStyle(.secondary) Text(label) Spacer() if let sc = shortcut { Text("⌘\(sc)").font(.caption).foregroundStyle(.tertiary) } }.contentShape(Rectangle()).padding(.horizontal,4).padding(.vertical,5) }.buttonStyle(.plain) } } struct MenuBarIcon: View { let controller: DaemonController? var body: some View { HStack(spacing:3) { Image(systemName: iconName).symbolRenderingMode(.palette).foregroundStyle(iconColor, .primary) if let u = controller?.serverState.uptime { Text(u).font(.system(size:10,design:.monospaced)) } } } private var iconName: String { switch controller?.serverState { case .running: return "dot.radiowaves.left.and.right"; case .starting: return "arrow.clockwise.circle" case .error: return "exclamationmark.circle"; default: return "antenna.radiowaves.left.and.right.slash" } } private var iconColor: Color { switch controller?.serverState { case .running: return .green; case .starting: return .yellow; case .error: return .red; default: return .secondary } } }