|
| 1 | +import type { ApplicationContext } from "../app/context.ts"; |
| 2 | +import { hasNativeUpdateBridge } from "../app/native-link-routing.ts"; |
| 3 | +import { confirmAndStartUpdate, type UpdateProgress } from "../app/update-confirmation.ts"; |
| 4 | +import { isUpdateActionable } from "../app/update-overlay-helpers.ts"; |
| 5 | +import { canCallGatewayMethod } from "../lib/gateway-methods.ts"; |
| 6 | +import { |
| 7 | + isUpdateAttentionForced, |
| 8 | + resolveUpdateAttentionDismissal, |
| 9 | +} from "./sidebar-attention-dismissals.ts"; |
| 10 | +import type { SidebarAttentionDismissal } from "./sidebar-attention-entries.ts"; |
| 11 | + |
| 12 | +type SidebarUpdateContext = Pick<ApplicationContext, "gateway" | "overlays">; |
| 13 | + |
| 14 | +export type SidebarUpdateAttentionState = { |
| 15 | + actionable: boolean; |
| 16 | + busy: boolean; |
| 17 | + canUpdate: boolean; |
| 18 | + dismissal: SidebarAttentionDismissal | null; |
| 19 | + forced: boolean; |
| 20 | + present: boolean; |
| 21 | +}; |
| 22 | + |
| 23 | +export function resolveSidebarUpdateAttention( |
| 24 | + context: SidebarUpdateContext, |
| 25 | +): SidebarUpdateAttentionState { |
| 26 | + const snapshot = context.overlays.snapshot; |
| 27 | + const campaign = snapshot.updateSchedule?.campaign; |
| 28 | + const busy = |
| 29 | + snapshot.updateRunning || |
| 30 | + snapshot.updateReconciliationPending || |
| 31 | + campaign?.state === "applying"; |
| 32 | + const canUpdate = canCallGatewayMethod(context.gateway.snapshot, "update.run", "operator.admin"); |
| 33 | + const canHydrateCampaign = canCallGatewayMethod( |
| 34 | + context.gateway.snapshot, |
| 35 | + "update.status", |
| 36 | + "operator.admin", |
| 37 | + ); |
| 38 | + const campaignPendingHydration = |
| 39 | + campaign && !snapshot.updateCampaignStatusHydrated && canHydrateCampaign; |
| 40 | + const present = snapshot.updateReconciliationPending |
| 41 | + ? true |
| 42 | + : campaignPendingHydration |
| 43 | + ? Boolean(snapshot.updateRunning || snapshot.updateStatusBanner) |
| 44 | + : Boolean( |
| 45 | + snapshot.updateRunning || |
| 46 | + snapshot.updateStatusBanner || |
| 47 | + snapshot.updateAvailable || |
| 48 | + campaign, |
| 49 | + ); |
| 50 | + const dismissal = resolveUpdateAttentionDismissal({ |
| 51 | + gatewayBootId: context.gateway.snapshot.hello?.server?.bootId, |
| 52 | + updateAvailable: snapshot.updateAvailable, |
| 53 | + updateSchedule: snapshot.updateSchedule, |
| 54 | + }); |
| 55 | + const forced = |
| 56 | + snapshot.updateRunning || |
| 57 | + snapshot.updateReconciliationPending || |
| 58 | + campaign?.state === "applying" || |
| 59 | + isUpdateAttentionForced(snapshot.updateStatusBanner?.tone); |
| 60 | + return { |
| 61 | + actionable: isUpdateActionable(snapshot.updateAvailable, snapshot.updateSchedule, busy), |
| 62 | + busy, |
| 63 | + canUpdate, |
| 64 | + dismissal, |
| 65 | + forced, |
| 66 | + present, |
| 67 | + }; |
| 68 | +} |
| 69 | + |
| 70 | +export function startSidebarUpdateAttention(params: { |
| 71 | + context: SidebarUpdateContext; |
| 72 | + nativeUpdateDeclined: boolean; |
| 73 | + watchUpdateProgress?: (listener: (progress: UpdateProgress) => void) => () => void; |
| 74 | +}) { |
| 75 | + const state = resolveSidebarUpdateAttention(params.context); |
| 76 | + if (!state.actionable || state.busy || !state.canUpdate) { |
| 77 | + return; |
| 78 | + } |
| 79 | + const snapshot = params.context.overlays.snapshot; |
| 80 | + void confirmAndStartUpdate({ |
| 81 | + startGatewayUpdate: () => void params.context.overlays.runUpdate(), |
| 82 | + ...(params.watchUpdateProgress ? { watchUpdateProgress: params.watchUpdateProgress } : {}), |
| 83 | + updateAvailable: snapshot.updateAvailable, |
| 84 | + updateSchedule: snapshot.updateSchedule, |
| 85 | + viaNativeApp: !params.nativeUpdateDeclined && hasNativeUpdateBridge(), |
| 86 | + }); |
| 87 | +} |
0 commit comments