116 lines
4.4 KiB
TypeScript
116 lines
4.4 KiB
TypeScript
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";
|
|
import { msg } from "../../lib/notifications";
|
|
|
|
// 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<DownloadJobKind, typeof CANCEL_DOWNLOAD> = {
|
|
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, msg(`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) => <I className="w-4 h-4" />;
|
|
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 };
|
|
}
|