feat(design): the download, notification and Scheduler action lists (C1) — the rows' "…", the band's and the strip's "…" (E28 S3e3)
A download row keeps play (pause / resume / retry in the queue) and the save link; the editors, sharing, re-download, the copies and cancel / delete / remove moved into its "…" (useDownloadActions). A link inside the row keeps the browser's own right-click menu. A notification row keeps its link and its unread mark and shows its dismiss on hover; an activity notice's Find in feed and Undo are in its "…". The inbox band keeps Mark all read and puts Clear everything, now behind a confirm, in its "…". The Scheduler strip keeps Run all and Pause and puts Purge discovery in its "…". Polled lists key their menus by id (keyOf). ui/menuGroups draws a rule only between two non-empty groups. Find in feed also drops the tag, search and age filters that could hide the video it looks for.
This commit is contained in:
@@ -31,6 +31,10 @@ import { Pressable } from "./ui/Pressable";
|
||||
import { Input } from "./ui/Input";
|
||||
import { SURFACE } from "./ui/surfaces";
|
||||
import { SectionLabel } from "./ui/Headings";
|
||||
import { useContextMenu } from "./ui/useContextMenu";
|
||||
import { ContextMenu } from "./ui/ContextMenu";
|
||||
import { MoreButton } from "./ui/MoreButton";
|
||||
import { type ActionMenuItem } from "./ui/ActionMenu";
|
||||
|
||||
const POLL_MS = 4000;
|
||||
// While any job is running, poll faster so short scheduled runs aren't missed between ticks.
|
||||
@@ -487,6 +491,10 @@ export default function Scheduler() {
|
||||
},
|
||||
});
|
||||
|
||||
// The page's own menu: the status strip's "…" (E28 C1). Run all and Pause stay in sight; the
|
||||
// destructive Purge discovery lives only here, behind its confirm.
|
||||
const pageMenu = useContextMenu<"scheduler">();
|
||||
|
||||
if (q.isError && !data)
|
||||
return (
|
||||
<StateMessage tone="error" onRetry={() => q.refetch()}>
|
||||
@@ -496,6 +504,45 @@ export default function Scheduler() {
|
||||
if (q.isLoading && !data) return <LoadingState label={t("scheduler.loading")} />;
|
||||
if (!data) return <div className="p-8 text-muted">{t("common.somethingWrong")}</div>;
|
||||
|
||||
async function purge() {
|
||||
const ok = await confirm({
|
||||
title: t("scheduler.purgeConfirmTitle"),
|
||||
message: t("scheduler.purgeDiscoveryHint"),
|
||||
confirmLabel: t("scheduler.purgeDiscovery"),
|
||||
danger: true,
|
||||
});
|
||||
if (ok) purgeMut.mutate();
|
||||
}
|
||||
const paused = data.paused;
|
||||
const pageItems: ActionMenuItem[] = [
|
||||
{
|
||||
id: "run-all",
|
||||
label: t("scheduler.runAll"),
|
||||
icon: <Play className="w-4 h-4" />,
|
||||
disabled: paused || runAllMut.isPending,
|
||||
note: paused ? t("scheduler.runAllPausedHint") : undefined,
|
||||
onSelect: () => runAllMut.mutate(),
|
||||
},
|
||||
{
|
||||
id: "pause",
|
||||
label: paused ? t("scheduler.resume") : t("scheduler.pause"),
|
||||
title: t("scheduler.pauseHint"),
|
||||
icon: paused ? <Play className="w-4 h-4" /> : <Pause className="w-4 h-4" />,
|
||||
disabled: pauseResume.isPending,
|
||||
onSelect: () => pauseResume.mutate(paused),
|
||||
},
|
||||
{
|
||||
id: "purge",
|
||||
label: t("scheduler.purgeDiscovery"),
|
||||
title: t("scheduler.purgeDiscoveryHint"),
|
||||
icon: <Trash2 className="w-4 h-4" />,
|
||||
danger: true,
|
||||
separatorBefore: true,
|
||||
disabled: purgeMut.isPending,
|
||||
onSelect: () => void purge(),
|
||||
},
|
||||
];
|
||||
|
||||
const quotaPct = data.quota.daily_budget
|
||||
? Math.min(100, Math.round((data.quota.used_today / data.quota.daily_budget) * 100))
|
||||
: 0;
|
||||
@@ -539,25 +586,6 @@ export default function Scheduler() {
|
||||
{t("scheduler.runAll")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip hint={t("scheduler.purgeDiscoveryHint")}>
|
||||
<Button
|
||||
variant="glass"
|
||||
size="sm"
|
||||
onClick={async () => {
|
||||
const ok = await confirm({
|
||||
title: t("scheduler.purgeConfirmTitle"),
|
||||
message: t("scheduler.purgeDiscoveryHint"),
|
||||
confirmLabel: t("scheduler.purgeDiscovery"),
|
||||
danger: true,
|
||||
});
|
||||
if (ok) purgeMut.mutate();
|
||||
}}
|
||||
disabled={purgeMut.isPending}
|
||||
>
|
||||
<Trash2 className="w-4 h-4" />
|
||||
{t("scheduler.purgeDiscovery")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip hint={t("scheduler.pauseHint")}>
|
||||
<Button
|
||||
variant="glass"
|
||||
@@ -569,6 +597,13 @@ export default function Scheduler() {
|
||||
{data.paused ? t("scheduler.resume") : t("scheduler.pause")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<MoreButton
|
||||
label={t("scheduler.menu")}
|
||||
variant="glass"
|
||||
expanded={pageMenu.open}
|
||||
menuProps={pageMenu.buttonProps("scheduler")}
|
||||
/>
|
||||
<ContextMenu menu={pageMenu} ariaLabel={t("scheduler.menu")} items={pageItems} />
|
||||
</div>
|
||||
|
||||
{!data.running_here && (
|
||||
|
||||
Reference in New Issue
Block a user