feat(design): a channel's and a Plex playlist's one action list (C1) — the row's "…", Delete + undo, an action toast's 8 s floor (E28 S3e2)
- Channels: the row's full-history, hide and unsubscribe buttons become its "…" at the row end (useChannelActions: open, pin, YouTube, copy name / link, full history or the admin reset, hide with an undo toast, unsubscribe behind its danger confirm); the card layout docks the "…" beside the name (ChannelLayoutGrid moreKey). The admin reset's confirm now names the reset. - Plex playlist page: a title, a show group and a season each open the page's one row menu (a title's is its usePlexCardActions list plus the removal); no X at rest; removals take an undo toast whose Undo puts every row back at its own index in one request (bulk add `positions`); j/k walk the visible rows, Delete removes the walked one; rename and delete in the header's "…". - notify: a toast with an action lives at least 8 s (the owner's call), whatever the toast setting.
This commit is contained in:
@@ -1,10 +1,9 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Check, Eye, EyeOff, History, UserMinus } from "lucide-react";
|
||||
import { Check } from "lucide-react";
|
||||
import type { ManagedChannel } from "../lib/api";
|
||||
import { formatNumber } from "../lib/format";
|
||||
import Tooltip from "./ui/Tooltip";
|
||||
import { isFullySynced } from "./channelSync";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
|
||||
// The Channels-manager cells that are components in their own right (they read i18n), split out of
|
||||
// channelColumns so that file holds only the column factory and its predicates (Fast Refresh).
|
||||
@@ -29,77 +28,3 @@ export function StoredCell({ c }: { c: ManagedChannel }) {
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function ActionsCell({
|
||||
c,
|
||||
isAdmin,
|
||||
canWrite,
|
||||
onHide,
|
||||
onDeep,
|
||||
onReset,
|
||||
onUnsubscribe,
|
||||
}: {
|
||||
c: ManagedChannel;
|
||||
isAdmin: boolean;
|
||||
canWrite: boolean;
|
||||
onHide: () => void;
|
||||
onDeep: () => void;
|
||||
onReset: () => void;
|
||||
onUnsubscribe: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
// Admins get a "reset" trigger that re-fetches the channel from scratch regardless of
|
||||
// state — always actionable. Regular users get the per-user "request full history" opt-in,
|
||||
// which only applies until the back-catalog is in (or already coming for everyone).
|
||||
const done = c.backfill_done;
|
||||
const requested = !done && c.deep_requested;
|
||||
const optInActionable = !done && !c.deep_in_queue;
|
||||
const backfillHint = isAdmin
|
||||
? t("channels.row.resetHint")
|
||||
: c.deep_in_queue
|
||||
? t("channels.row.queuedByOtherHint")
|
||||
: requested
|
||||
? t("channels.row.queuedRequestedHint")
|
||||
: done
|
||||
? t("channels.row.fullHint")
|
||||
: t("channels.row.backfillThisHint");
|
||||
const enabled = isAdmin || optInActionable;
|
||||
return (
|
||||
<div className="inline-flex items-center gap-2">
|
||||
{/* Unavailable with a reason (queued by someone else, already full): aria-disabled keeps it
|
||||
focusable, so the hint that says why is reachable by keyboard too. */}
|
||||
<Tooltip hint={backfillHint} always={!enabled}>
|
||||
<IconButton
|
||||
onClick={isAdmin ? onReset : onDeep}
|
||||
aria-disabled={!enabled}
|
||||
variant={requested ? "ghost-accent" : "ghost"}
|
||||
label={isAdmin ? t("channels.row.resetBackfill") : t("channels.row.backfillThis")}
|
||||
noTitle
|
||||
>
|
||||
<History />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip hint={c.hidden ? t("channels.row.hiddenHint") : t("channels.row.hideHint")}>
|
||||
<IconButton
|
||||
onClick={onHide}
|
||||
label={c.hidden ? t("channels.row.unhide") : t("channels.row.hideFromFeed")}
|
||||
noTitle
|
||||
>
|
||||
{c.hidden ? <EyeOff /> : <Eye />}
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{canWrite && (
|
||||
<Tooltip hint={t("channels.row.unsubscribeHint")}>
|
||||
<IconButton
|
||||
variant="danger"
|
||||
onClick={onUnsubscribe}
|
||||
label={t("channels.row.unsubscribeOnYoutube")}
|
||||
noTitle
|
||||
>
|
||||
<UserMinus />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user