Files
siftlode/frontend/src/lib/downloads.ts
T
peter 4aaaaa7860 chore(comments): drop epic, sprint and review-round ids from the code
The code cited its plan instead of saying why: `E28 review J4`, `(R6 S3b)`, `S1 justified…`,
`deep-review round 5`. That plan lives in the private ops repo, so for every reader of this one the
citation was noise, and a sentence whose subject was the plan step ("round 2 showed…") hid its
lesson behind an id. Each one is gone and the "why" kept: a bare citation is deleted, a sentence
carrying a lesson is reworded around it ("a later attempt showed…").

Measured (`scripts/audit_metrics.py`, product code): 652 lines in 270 files carried an epic code by
the definition the next commit holds (507 / 241 by the old, two-digit-only one) -> 0. Across all
code, tests, scripts and configs: ~1100 epic-code lines plus ~320 sprint/round ids.

Behaviour unchanged, checked mechanically: every changed .py file has the same AST once docstrings
are blanked; css/sh/toml/yml/json files are identical once comments are stripped; the only
non-comment changes in TS/JS are test titles, one description string in a contract table, and five
ESLint messages, whose text is all the gates snapshot diff holds. Not touched: Markdown (docs and
the changelog keep their history), Alembic migrations, TV episode notation (`S2 · E5`), DESIGN.md
ids (`C1`, `B4`, `S3h`), tracker ids (`SEC-16`, `DX-17`).

One stale claim fixed on the way: test_hls_reaper's docstring said the player checkpoints "every
10 s whether or not it is playing", which plex/stream.py calls wrong; it now says what the code does.
2026-10-05 00:19:39 +02:00

54 lines
2.2 KiB
TypeScript

import type { QueryClient } from "@tanstack/react-query";
import { qk } from "./queryKeys";
import { api } from "./api";
import { copyText } from "./clipboard";
import { notify } from "./notifications";
import { notifyActionError } from "./actionErrors";
// The download queue, the per-card download index, and the storage-usage meter all move together
// whenever a job is enqueued, deleted, or its state changes — invalidate them as one unit.
export function invalidateDownloads(qc: QueryClient) {
qc.invalidateQueries({ queryKey: qk.downloads() });
qc.invalidateQueries({ queryKey: qk.downloadIndex() });
qc.invalidateQueries({ queryKey: qk.downloadUsage() });
}
// One-click share: the row's Share button turns "share this" into a single action.
// Ensure the job has a public link (minting one on demand — omitting `allow_download` so the backend
// default makes it downloadable), copy it to the clipboard, and toast with an "open in new tab"
// action. A copy failure still toasts (warning) WITH that action, so the link is never a dead end.
// Advanced controls (password, expiry, revoke, share-with-a-user) live in the Share dialog. Returns
// the shared URL, or null if link creation failed — so the caller/tests can react to the outcome.
export async function quickShareLink(
qc: QueryClient,
job: { id: number },
t: (key: string) => string,
): Promise<string | null> {
let link;
try {
const existing = await qc.fetchQuery({
queryKey: qk.downloadLinks(job.id),
queryFn: () => api.downloadLinks(job.id),
});
link = existing[0];
if (!link) {
link = await api.createDownloadLink(job.id, {}); // omit allow_download → backend default (true)
qc.setQueryData(qk.downloadLinks(job.id), [link]);
}
} catch (e) {
notifyActionError(e, t("downloads.share.linkFailed"));
return null;
}
const url = window.location.origin + link.url;
const action = {
label: t("downloads.share.openNewTab"),
onClick: () => window.open(url, "_blank", "noopener,noreferrer"),
};
notify(
(await copyText(url))
? { level: "success", message: t("downloads.share.linkCopied"), action }
: { level: "warning", message: t("downloads.share.copyFailed"), action },
);
return url;
}