import { useAtomValue } from "@effect/atom-react"; import { EnvironmentId } from "@t3tools/contracts"; import { Atom } from "effect/reactivity"; import { ChevronRightIcon } from "lucide-react"; import { Fragment, type RefObject, useRef } from "react"; import { environmentThreadShells } from "~/state/threads"; import { AlertDialog, AlertDialogClose, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogPopup, AlertDialogTitle, } from "../ui/alert-dialog"; import { Button } from "../ui/button"; import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from "../ui/collapsible"; import { MiddleTruncate } from "../ui/middle-truncate"; import { ScrollArea } from "../ui/scroll-area"; import type { RestartingMachine, UpdateEverythingRow } from "./updateEverything.logic"; const isRunning = (status: string) => status === "preparing" || status === "starting" || status === "running"; const runningThreadCountAtom = Atom.family((environmentIds: string) => Atom.make((get) => environmentIds .split("\n") .filter((id) => id !== "") .reduce( (count, id) => count + get(environmentThreadShells.environmentThreadsAtom(EnvironmentId.make(id))).filter( (thread) => isRunning(thread.status), ).length, 0, ), ).pipe(Atom.withLabel(`update-everything-running-threads:${environmentIds}`)), ); interface UpdateEverythingDialogProps { readonly open: boolean; readonly onOpenChange: (open: boolean) => void; readonly version: string | null; readonly providerCount: number; readonly rows: ReadonlyArray; readonly restarts: ReadonlyArray; readonly onConfirm: () => void; /** Overrides the "Update N machines?" title, for an update-track switch. */ readonly title?: string; readonly confirmLabel?: string; readonly notes?: ReadonlyArray; /** Shown in place of running-thread counts; disables the confirm button. */ readonly disabledReason?: string | null; /** The confirmed work is starting; it cannot be cancelled from here. */ readonly busy?: boolean; } type UpdateEverythingDialogContentProps = Omit< UpdateEverythingDialogProps, "open" | "onOpenChange" > & { readonly cancelRef: RefObject; }; export function UpdateEverythingDialog({ open, onOpenChange, ...content }: UpdateEverythingDialogProps) { const cancelRef = useRef(null); return ( {/* Refocusing the trigger after a pointer close shows its focus ring and opens its tooltip. */} closeType === "keyboard"} > ); } const APP_LABELS = { "this-app": "This app", server: "Server", "desktop-app": "Desktop app", } as const; function UpdateEverythingDialogContent({ version, providerCount, rows, restarts, onConfirm, cancelRef, title, confirmLabel = "Update", notes = [], disabledReason = null, busy = false, }: UpdateEverythingDialogContentProps) { const stopped = useAtomValue( runningThreadCountAtom( restarts .filter((machine) => !machine.continuesThreads) .map((machine) => machine.environmentId) .join("\n"), ), ); const continuing = useAtomValue( runningThreadCountAtom( restarts .filter((machine) => machine.continuesThreads) .map((machine) => machine.environmentId) .join("\n"), ), ); const running = stopped + continuing; const threads = `${running} running ${running === 1 ? "thread" : "threads"}`; const restarting = new Set(restarts.map((machine) => machine.environmentId)); const updating = rows.filter((row) => row.app !== null || row.providers.length > 0); const skipped = rows.filter((row) => row.skipped !== null); return ( <> {title ?? `Update ${updating.length} ${updating.length === 1 ? "machine" : "machines"}?`} {version !== null ? ( <> T3 Code {version} ) : null} {version !== null && providerCount > 0 ? " ยท " : null} {providerCount > 0 ? `${providerCount} provider ${providerCount === 1 ? "update" : "updates"}` : null}
{notes.map((note) => (

{note}

))} {/* Machine names truncate first. Provider lists wrap between names, never inside one. */}
Machine T3 Code Providers
{updating.map((row) => (
{row.app !== null ? APP_LABELS[row.app] : null} {row.app === null && row.skipped !== null ? ( Skipped ) : null} {row.app === "this-app" ? ( restarts last ) : null} {row.providers.map((provider, index) => ( {provider} {index < row.providers.length - 1 ? ", " : null} ))} {row.environmentId !== null && restarting.has(row.environmentId) ? ( ) : null}
))}
{skipped.length > 0 ? ( {skipped.length} skipped
{skipped.map((row) => ( {row.skipped} ))}
) : null}
{disabledReason !== null ? (

{disabledReason}

) : running > 0 ? (

{stopped === 0 ? `${threads} will be interrupted and can resume after the restart.` : continuing === 0 ? `${threads} will be interrupted.` : `${threads} will be interrupted. ${continuing} can resume after the restart.`}

) : null} }> Cancel
); } function RunningThreads({ environmentId }: { readonly environmentId: EnvironmentId }) { const count = useAtomValue(runningThreadCountAtom(environmentId)); return count > 0 ? {count} running : null; }