mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 11:41:34 +00:00
* feat(mac): add gateway menu surfaces * docs(mac): document gateway menu surfaces * test(mac): make dashboard frame assertions display-independent
96 lines
3.2 KiB
Swift
96 lines
3.2 KiB
Swift
import AppKit
|
|
|
|
@MainActor
|
|
final class MenuGatewaysInjector: NSObject {
|
|
static let shared = MenuGatewaysInjector()
|
|
|
|
private let tag = 9_415_559
|
|
|
|
func inject(into menu: NSMenu) {
|
|
for item in menu.items where item.tag == self.tag {
|
|
menu.removeItem(item)
|
|
}
|
|
|
|
let items = DashboardGatewayMenuModel.items(from: DashboardManager.shared.gatewayEntries)
|
|
guard !items.isEmpty,
|
|
var cursor = menu.items.lastIndex(where: \.isSeparatorItem)
|
|
else {
|
|
return
|
|
}
|
|
|
|
let header = NSMenuItem.sectionHeader(title: String(localized: "Gateways"))
|
|
header.tag = self.tag
|
|
menu.insertItem(header, at: cursor)
|
|
cursor += 1
|
|
|
|
for gateway in items {
|
|
let item = NSMenuItem(
|
|
title: gateway.name,
|
|
action: #selector(self.openGateway(_:)),
|
|
keyEquivalent: "")
|
|
item.tag = self.tag
|
|
item.target = self
|
|
item.representedObject = gateway.id
|
|
item.image = Self.healthImage(for: gateway.health, name: gateway.name)
|
|
item.state = gateway.isPrimary ? .on : .off
|
|
menu.insertItem(item, at: cursor)
|
|
cursor += 1
|
|
|
|
if gateway.canPromote {
|
|
let alternate = NSMenuItem(
|
|
title: String(localized: "Set \(gateway.name) as Primary…"),
|
|
action: #selector(self.setPrimary(_:)),
|
|
keyEquivalent: "")
|
|
alternate.tag = self.tag
|
|
alternate.target = self
|
|
alternate.representedObject = gateway.id
|
|
alternate.isAlternate = true
|
|
alternate.keyEquivalentModifierMask = [.option]
|
|
menu.insertItem(alternate, at: cursor)
|
|
cursor += 1
|
|
}
|
|
}
|
|
}
|
|
|
|
private static func healthImage(for health: DashboardGatewayHealth, name: String) -> NSImage? {
|
|
let (symbolName, color): (String, NSColor) = switch health {
|
|
case .ok:
|
|
("circle.fill", .systemGreen)
|
|
case .error:
|
|
("exclamationmark.circle.fill", .systemRed)
|
|
case .unknown:
|
|
("circle", .tertiaryLabelColor)
|
|
}
|
|
let accessibilityDescription = switch health {
|
|
case .ok:
|
|
String(localized: "\(name), healthy")
|
|
case .error:
|
|
String(localized: "\(name), health error")
|
|
case .unknown:
|
|
String(localized: "\(name), health unknown")
|
|
}
|
|
let image = NSImage(systemSymbolName: symbolName, accessibilityDescription: accessibilityDescription)
|
|
return image?.withSymbolConfiguration(.init(paletteColors: [color]))
|
|
}
|
|
|
|
@objc
|
|
private func openGateway(_ sender: NSMenuItem) {
|
|
guard let id = sender.representedObject as? String,
|
|
let target = DashboardGatewayTarget(bridgeID: id)
|
|
else {
|
|
return
|
|
}
|
|
DashboardManager.shared.openOrFocusDashboard(for: target)
|
|
}
|
|
|
|
@objc
|
|
private func setPrimary(_ sender: NSMenuItem) {
|
|
guard let id = sender.representedObject as? String,
|
|
let target = DashboardGatewayTarget(bridgeID: id)
|
|
else {
|
|
return
|
|
}
|
|
DashboardManager.shared.confirmSetPrimary(target)
|
|
}
|
|
}
|