import { useMemo, useRef, useState } from "react"; import clsx from "clsx"; import { BarButton, IconButton, Popup, ProgressBar, showContextMenu, T, tooltipAttrs } from "@/ui"; import { useContributions, useStatusInfo, type StatusItemContribution } from "./contributions"; import { ErrorBoundary } from "./ErrorBoundary"; import { isStatusItemVisible, statusBarMenu, useStatusWidgetOverrides } from "./statusBarWidgets"; import { cancelTask, canCancel, useTasks, type BackgroundTask } from "./tasks"; import styles from "./StatusBar.module.css"; import { tx } from "@/platform/i18n"; /** * Orders status items: on both sides a higher priority sits closer to the * window edge, so left items sort descending and right items ascending. */ export function orderStatusItems(items: Iterable) { const all = [...items]; const left = all .filter((i) => i.align === "left") .sort((a, b) => b.priority - a.priority || a.id.localeCompare(b.id)); const right = all .filter((i) => i.align === "right") .sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id)); return { left, right }; } /** `lightEdit`: a LightEdit window's bar, with the items that apply there only. */ export function StatusBar({ lightEdit = false }: { lightEdit?: boolean }) { const map = useContributions((s) => s.statusItems); const overrides = useStatusWidgetOverrides(); const { left, right } = useMemo( () => orderStatusItems([...map.values()].filter((i) => !lightEdit || i.lightEdit)), [map, lightEdit], ); const visible = (i: StatusItemContribution) => isStatusItemVisible(i, overrides); const render = (item: StatusItemContribution) => { const C = item.component; const shrink = item.align === "left" || item.id === "workbench.tasks"; return (
); }; return ( ); } /** Built-in: the transient status message (`showStatusInfo`). */ export function StatusInfo() { const text = useStatusInfo((s) => s.text); if (!text) return null; return ( {text} ); } /** A task's cancel button: working, "Cancelling…", or disabled with the reason it can't be stopped. */ function TaskCancel({ task: t }: { task: BackgroundTask }) { if (t.done) return null; if (canCancel(t)) return cancelTask(t.id)} />; const reason = t.cancelling ? tx("Cancelling…") : t.uncancellable; if (!reason) return null; // A disabled button gets no pointer events; the wrapper shows why. return ( ); } /** Built-in: background task progress; click lists all running tasks. */ export function TaskStatus() { const tasks = useTasks((s) => s.tasks); const [open, setOpen] = useState(false); const ref = useRef(null); const running = tasks.filter((t) => !t.done); // A task that is working wins over the queued ones waiting behind it. const current = running.findLast((t) => !t.queued) ?? running[running.length - 1] ?? tasks[tasks.length - 1]; if (!current) return null; const more = running.length - 1; return ( <> setOpen((v) => !v)} className={styles.task} > {current.title} {more > 0 && +{more}} 0} anchor={ref} placement="top-end" offset={6} onClose={() => setOpen(false)} aria-label="Background tasks" >
Background Tasks
{tasks.map((t) => (
{t.title} {t.fraction != null && {Math.round(t.fraction * 100)}%}
{t.message &&
{t.message}
}
))}
); }