import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; import { Ban, Pause, Play, RefreshCw } from "lucide-react"; import { api, type DownloadJob, type DownloadJobKind } from "../../lib/api"; import { notifyActionError } from "../../lib/actionErrors"; import { invalidateDownloads } from "../../lib/downloads"; import { useConfirm } from "../../components/ui/confirmContext"; import { useCopyItems } from "../../components/ui/copyItems"; import { jobTitle } from "./downloadTitle"; import { type ActionMenuItem } from "../../components/ui/ActionMenu"; import { menuGroups } from "../../components/ui/menuGroups"; // A queued download's ONE action list: the row's "…", its right-click, long-press and Shift+F10. The // row keeps its primary action (pause / resume / retry) and its state (the error and its details) in // sight; the copies and Cancel live here. A finished download is a library file: its actions are // the Files page's (useFileActions). export type JobOp = "pause" | "resume" | "cancel"; // The Cancel confirmation of a queued job; an upload's names what it deletes. const CANCEL_DOWNLOAD = { title: "downloads.confirm.cancelTitle", body: "downloads.confirm.cancelBody", action: "downloads.confirm.cancelAction", }; const CANCEL_UPLOAD = { title: "downloads.confirm.cancelUploadTitle", body: "downloads.confirm.cancelUploadBody", action: "downloads.confirm.cancelUploadAction", }; const CANCEL_EDIT = { title: "downloads.confirm.cancelEditTitle", body: "downloads.confirm.cancelEditBody", action: "downloads.confirm.cancelEditAction", }; // Cancelling an upload deletes the file it uploaded (there is nothing to fetch again), so its // confirmation says so — "stop the download" would read as safe to undo. A clip is being cut, not // downloaded. const CANCEL_COPY: Record = { download: CANCEL_DOWNLOAD, upload: CANCEL_UPLOAD, edit: CANCEL_EDIT, clip: CANCEL_EDIT, // both are a clip being cut: the same words }; export function useDownloadActions() { const qc = useQueryClient(); const { t } = useTranslation(); const confirm = useConfirm(); const copy = useCopyItems(); const act = useMutation({ mutationFn: ({ id, op }: { id: number; op: JobOp }) => op === "pause" ? api.pauseDownload(id) : op === "resume" ? api.resumeDownload(id) : api.cancelDownload(id), onSuccess: () => invalidateDownloads(qc), onError: (e, v) => notifyActionError(e, t(`errors.writeFailed.job.${v.op}`)), }); const confirmThen = async (title: string, body: string, action: string, fn: () => void) => { if (await confirm({ title, message: body, confirmLabel: action, danger: true })) fn(); }; const icon = (I: typeof Play) => ; const copies = (job: DownloadJob) => copy(jobTitle(job), job.source_url); function queueItems(job: DownloadJob): ActionMenuItem[] { const running = job.status === "queued" || job.status === "running"; return menuGroups( [ running ? { id: "pause", label: t("downloads.actions.pause"), icon: icon(Pause), onSelect: () => act.mutate({ id: job.id, op: "pause" }), } : { id: "resume", label: t( job.status === "error" ? "downloads.actions.retry" : "downloads.actions.resume", ), icon: icon(job.status === "error" ? RefreshCw : Play), onSelect: () => act.mutate({ id: job.id, op: "resume" }), }, ], copies(job), [ { id: "cancel", label: t("downloads.actions.cancel"), icon: icon(Ban), danger: true, onSelect: () => { const copy = CANCEL_COPY[job.job_kind ?? "download"]; void confirmThen(t(copy.title), t(copy.body), t(copy.action), () => act.mutate({ id: job.id, op: "cancel" }), ); }, }, ], ); } /** The job's menu rows. */ const items = (job: DownloadJob): ActionMenuItem[] => queueItems(job); /** The queue's visible primary action and the error dialog's Retry. */ const run = (job: DownloadJob, op: JobOp) => act.mutate({ id: job.id, op }); return { items, run }; }