chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,267 @@
|
||||
import AppKit
|
||||
import CodexBarCore
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
struct MenuContent: View {
|
||||
@Bindable var store: UsageStore
|
||||
@Bindable var settings: SettingsStore
|
||||
let account: AccountInfo
|
||||
let updater: UpdaterProviding
|
||||
let provider: UsageProvider?
|
||||
let actions: MenuActions
|
||||
|
||||
var body: some View {
|
||||
let descriptor = MenuDescriptor.build(
|
||||
provider: self.provider,
|
||||
store: self.store,
|
||||
settings: self.settings,
|
||||
account: self.account,
|
||||
updateReady: self.updater.updateStatus.isUpdateReady)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
ForEach(Array(descriptor.sections.enumerated()), id: \.offset) { index, section in
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
ForEach(Array(section.entries.enumerated()), id: \.offset) { _, entry in
|
||||
self.row(for: entry)
|
||||
}
|
||||
}
|
||||
if index < descriptor.sections.count - 1 {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 6)
|
||||
.frame(minWidth: 260, alignment: .leading)
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private func row(for entry: MenuDescriptor.Entry) -> some View {
|
||||
switch entry {
|
||||
case let .text(text, style):
|
||||
switch style {
|
||||
case .headline:
|
||||
Text(text).font(.headline)
|
||||
.accessibilityLabel(text)
|
||||
case .primary:
|
||||
Text(text)
|
||||
.accessibilityLabel(text)
|
||||
case .secondary:
|
||||
Text(text).foregroundStyle(.secondary).font(.footnote)
|
||||
.accessibilityLabel(text)
|
||||
}
|
||||
case let .action(title, action):
|
||||
Button {
|
||||
self.perform(action)
|
||||
} label: {
|
||||
if let icon = self.iconName(for: action) {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: icon)
|
||||
.imageScale(.medium)
|
||||
.frame(width: 18, alignment: .center)
|
||||
Text(title)
|
||||
}
|
||||
.foregroundStyle(.primary)
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(title)
|
||||
} else {
|
||||
Text(title)
|
||||
.accessibilityLabel(title)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
case let .unavailable(title, tooltip):
|
||||
Text(title)
|
||||
.foregroundStyle(.secondary)
|
||||
.help(tooltip ?? "")
|
||||
case let .submenu(title, systemImageName, submenuItems):
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack(spacing: 8) {
|
||||
if let systemImageName {
|
||||
Image(systemName: systemImageName)
|
||||
}
|
||||
Text(title).font(.headline)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(title)
|
||||
ForEach(Array(submenuItems.enumerated()), id: \.offset) { _, submenuItem in
|
||||
HStack(spacing: 8) {
|
||||
if submenuItem.isChecked {
|
||||
Image(systemName: "checkmark")
|
||||
.imageScale(.small)
|
||||
.frame(width: 18, alignment: .center)
|
||||
} else {
|
||||
Spacer().frame(width: 18)
|
||||
}
|
||||
Text(submenuItem.title)
|
||||
.foregroundStyle(submenuItem.isEnabled ? .primary : .secondary)
|
||||
}
|
||||
.accessibilityElement(children: .combine)
|
||||
.accessibilityLabel(submenuItem.title)
|
||||
}
|
||||
}
|
||||
case .divider:
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
private func iconName(for action: MenuDescriptor.MenuAction) -> String? {
|
||||
action.systemImageName
|
||||
}
|
||||
|
||||
private func perform(_ action: MenuDescriptor.MenuAction) {
|
||||
switch action {
|
||||
case .refresh:
|
||||
self.actions.refresh()
|
||||
case .refreshAugmentSession:
|
||||
self.actions.refreshAugmentSession()
|
||||
case .installUpdate:
|
||||
self.actions.installUpdate()
|
||||
case .dashboard:
|
||||
self.actions.openDashboard()
|
||||
case .statusPage:
|
||||
self.actions.openStatusPage()
|
||||
case .changelog:
|
||||
self.actions.openChangelog()
|
||||
case .addCodexAccount:
|
||||
self.actions.addCodexAccount()
|
||||
case .requestCodexSystemPromotion:
|
||||
return
|
||||
case let .addProviderAccount(provider):
|
||||
self.actions.switchAccount(provider)
|
||||
case let .switchAccount(provider):
|
||||
self.actions.switchAccount(provider)
|
||||
case let .openTerminal(command):
|
||||
self.actions.openTerminal(command)
|
||||
case let .loginToProvider(url):
|
||||
if let urlObj = URL(string: url) {
|
||||
NSWorkspace.shared.open(urlObj)
|
||||
}
|
||||
case .settings:
|
||||
self.actions.openSettings()
|
||||
case .about:
|
||||
self.actions.openAbout()
|
||||
case .quit:
|
||||
self.actions.quit()
|
||||
case let .copyError(message):
|
||||
self.actions.copyError(message)
|
||||
case .focusAgentSession:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MenuActions {
|
||||
let installUpdate: () -> Void
|
||||
let refresh: () -> Void
|
||||
let refreshAugmentSession: () -> Void
|
||||
let openDashboard: () -> Void
|
||||
let openStatusPage: () -> Void
|
||||
let openChangelog: () -> Void
|
||||
let addCodexAccount: () -> Void
|
||||
let switchAccount: (UsageProvider) -> Void
|
||||
let openTerminal: (String) -> Void
|
||||
let openSettings: () -> Void
|
||||
let openAbout: () -> Void
|
||||
let quit: () -> Void
|
||||
let copyError: (String) -> Void
|
||||
}
|
||||
|
||||
struct PersistentRefreshRowMetrics: Equatable {
|
||||
static let defaults = Self(
|
||||
rowHeight: 24,
|
||||
selectionHorizontalInset: 5,
|
||||
selectionVerticalInset: 0,
|
||||
selectionCornerRadius: 7,
|
||||
// Align the custom row's image/title frames with native NSMenuItem columns.
|
||||
leadingPadding: 15,
|
||||
trailingPadding: 8,
|
||||
iconWidth: 16,
|
||||
iconSymbolPointSize: 16,
|
||||
iconSymbolWeight: .regular,
|
||||
iconTitleSpacing: 4.5,
|
||||
shortcutFontSize: 13,
|
||||
shortcutXOffset: -9.5,
|
||||
shortcutYOffset: 0)
|
||||
|
||||
let rowHeight: CGFloat
|
||||
let selectionHorizontalInset: CGFloat
|
||||
let selectionVerticalInset: CGFloat
|
||||
let selectionCornerRadius: CGFloat
|
||||
let leadingPadding: CGFloat
|
||||
let trailingPadding: CGFloat
|
||||
let iconWidth: CGFloat
|
||||
let iconSymbolPointSize: CGFloat
|
||||
let iconSymbolWeight: NSFont.Weight
|
||||
let iconTitleSpacing: CGFloat
|
||||
let shortcutFontSize: CGFloat
|
||||
let shortcutXOffset: CGFloat
|
||||
let shortcutYOffset: CGFloat
|
||||
}
|
||||
|
||||
@MainActor
|
||||
struct StatusIconView: View {
|
||||
@Bindable var store: UsageStore
|
||||
let provider: UsageProvider
|
||||
|
||||
var body: some View {
|
||||
Image(nsImage: self.icon)
|
||||
.renderingMode(.template)
|
||||
.interpolation(.none)
|
||||
.accessibilityLabel(self.accessibilityLabel)
|
||||
.accessibilityValue(self.accessibilityValue)
|
||||
}
|
||||
|
||||
private var accessibilityLabel: String {
|
||||
let descriptor = ProviderDescriptorRegistry.descriptor(for: self.provider)
|
||||
return descriptor.metadata.displayName
|
||||
}
|
||||
|
||||
private var accessibilityValue: String {
|
||||
let snapshot = self.store.snapshot(for: self.provider)
|
||||
guard let snap = snapshot else {
|
||||
return L("No data")
|
||||
}
|
||||
let remaining = IconRemainingResolver.resolvedRemaining(
|
||||
snapshot: snap,
|
||||
style: self.store.style(for: self.provider))
|
||||
let primary = remaining.primary
|
||||
let percent = primary.map(Self.accessibilityPercentRemaining) ?? L("Unknown")
|
||||
let stale = self.store.isStale(provider: self.provider)
|
||||
return stale ? "\(percent), \(L("stale data"))" : percent
|
||||
}
|
||||
|
||||
static func accessibilityPercentRemaining(_ remaining: Double) -> String {
|
||||
String(format: L("%d percent remaining"), Int(remaining.rounded()))
|
||||
}
|
||||
|
||||
private var icon: NSImage {
|
||||
let now = Date()
|
||||
let snapshot = self.store.snapshot(for: self.provider)
|
||||
let remaining = snapshot.map {
|
||||
IconRemainingResolver.resolvedRemaining(
|
||||
snapshot: $0,
|
||||
style: self.store.style(for: self.provider),
|
||||
now: now)
|
||||
}
|
||||
let creditsProjection = self.store.codexConsumerProjectionIfNeeded(
|
||||
for: self.provider,
|
||||
surface: .menuBar,
|
||||
snapshotOverride: snapshot,
|
||||
now: now)
|
||||
let creditsRemaining = creditsProjection?.menuBarFallback == .creditsBalance
|
||||
? self.store.codexMenuBarCreditsRemaining(
|
||||
snapshotOverride: snapshot,
|
||||
now: now)
|
||||
: nil
|
||||
return IconRenderer.makeIcon(
|
||||
primaryRemaining: remaining?.primary,
|
||||
weeklyRemaining: remaining?.secondary,
|
||||
creditsRemaining: creditsRemaining,
|
||||
stale: self.store.isStale(provider: self.provider),
|
||||
style: self.store.style(for: self.provider),
|
||||
statusIndicator: self.store.statusIndicator(for: self.provider),
|
||||
hideCritters: self.store.settings.menuBarHidesCritters)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user