chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
import CodexBarCore
|
||||
import Observation
|
||||
|
||||
struct MenuCardLiveSubtitle {
|
||||
let text: String
|
||||
let style: UsageMenuCardView.Model.SubtitleStyle
|
||||
}
|
||||
|
||||
/// Updates values in an already-hosted card without rebuilding its tracked NSMenu.
|
||||
@MainActor
|
||||
@Observable
|
||||
final class MenuCardRefreshMonitor {
|
||||
typealias ModelResolver = @MainActor (UsageProvider) -> UsageMenuCardView.Model?
|
||||
typealias ProviderRefreshStateResolver = @MainActor (UsageProvider) -> Bool
|
||||
|
||||
private let resolveModel: ModelResolver
|
||||
private let isProviderRefreshActive: ProviderRefreshStateResolver
|
||||
/// Set while an all-providers refresh is running; individual cards freeze only while their
|
||||
/// provider has active refresh work.
|
||||
private var globalManualRefreshInFlight = false
|
||||
/// Providers with an individual manual refresh in flight. Concurrent entries are allowed so
|
||||
/// refreshing one provider does not stall or unfreeze another.
|
||||
private var manualRefreshProviders: Set<UsageProvider> = []
|
||||
private var frozenManualRefreshModels: [UsageProvider: UsageMenuCardView.Model] = [:]
|
||||
|
||||
/// True while any manual refresh (global or per-provider) is running.
|
||||
var isManualRefreshInFlight: Bool {
|
||||
self.globalManualRefreshInFlight || !self.manualRefreshProviders.isEmpty
|
||||
}
|
||||
|
||||
init(
|
||||
resolveModel: @escaping ModelResolver,
|
||||
isProviderRefreshActive: @escaping ProviderRefreshStateResolver)
|
||||
{
|
||||
self.resolveModel = resolveModel
|
||||
self.isProviderRefreshActive = isProviderRefreshActive
|
||||
}
|
||||
|
||||
func beginManualRefresh(
|
||||
frozenModels: [UsageProvider: UsageMenuCardView.Model],
|
||||
provider: UsageProvider? = nil)
|
||||
{
|
||||
if let provider {
|
||||
self.frozenManualRefreshModels[provider] = frozenModels[provider]
|
||||
self.manualRefreshProviders.insert(provider)
|
||||
} else {
|
||||
self.frozenManualRefreshModels = frozenModels
|
||||
self.globalManualRefreshInFlight = true
|
||||
}
|
||||
}
|
||||
|
||||
/// Balances a `beginManualRefresh` with the same `provider` argument (nil ends the global refresh).
|
||||
func endManualRefresh(for provider: UsageProvider? = nil) {
|
||||
if let provider {
|
||||
self.manualRefreshProviders.remove(provider)
|
||||
self.frozenManualRefreshModels[provider] = nil
|
||||
} else {
|
||||
self.globalManualRefreshInFlight = false
|
||||
self.frozenManualRefreshModels.removeAll(keepingCapacity: true)
|
||||
}
|
||||
}
|
||||
|
||||
func resetManualRefresh() {
|
||||
self.globalManualRefreshInFlight = false
|
||||
self.manualRefreshProviders.removeAll(keepingCapacity: true)
|
||||
self.frozenManualRefreshModels.removeAll(keepingCapacity: true)
|
||||
}
|
||||
|
||||
func isManualRefreshInFlight(for provider: UsageProvider) -> Bool {
|
||||
self.manualRefreshProviders.contains(provider) ||
|
||||
(self.globalManualRefreshInFlight && self.isProviderRefreshActive(provider))
|
||||
}
|
||||
|
||||
func model(
|
||||
for provider: UsageProvider,
|
||||
fallback: UsageMenuCardView.Model) -> UsageMenuCardView.Model
|
||||
{
|
||||
guard !self.isManualRefreshInFlight(for: provider) else {
|
||||
guard let frozen = self.frozenManualRefreshModels[provider] else {
|
||||
return fallback
|
||||
}
|
||||
if fallback.hasCompatibleTrackedLayout(with: frozen) {
|
||||
return frozen
|
||||
}
|
||||
// A rebuilding menu may temporarily lose some metric rows, but retained rows and other sections
|
||||
// must still match the frozen layout.
|
||||
if fallback.hasCompatibleTrackedMetricSubset(of: frozen) {
|
||||
return frozen
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
guard let resolved = self.resolveModel(provider),
|
||||
fallback.hasCompatibleTrackedLayout(with: resolved)
|
||||
else {
|
||||
return fallback
|
||||
}
|
||||
return resolved
|
||||
}
|
||||
|
||||
func subtitle(
|
||||
for provider: UsageProvider,
|
||||
fallback: MenuCardLiveSubtitle) -> MenuCardLiveSubtitle
|
||||
{
|
||||
if self.isManualRefreshInFlight(for: provider) {
|
||||
return MenuCardLiveSubtitle(text: "\(L("Refreshing"))…", style: .loading)
|
||||
}
|
||||
guard let model = self.resolveModel(provider) else { return fallback }
|
||||
return MenuCardLiveSubtitle(text: model.subtitleText, style: model.subtitleStyle)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user