fix(downloads): an upload's cancel, progress, failure and notice speak of an upload

- Cancelling an upload asks "Delete upload?" and says the uploaded file is
  removed from the server with it.
- The phone-safe conversion's real ffmpeg % shows as a bar ("Converting for
  phones"), not an indeterminate pulse.
- A failed upload is titled "The upload isn't ready", with its own reasons
  (convert_failed, retryable; upload_missing) instead of download copy.
- The inbox says "Upload ready to share" for a converted upload.
This commit is contained in:
2026-10-06 03:19:32 +02:00
parent dba4758c59
commit 6073a006b8
14 changed files with 157 additions and 21 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ export interface DownloadJob {
display_uploader: string | null; // user override of the channel name
display_uploader_url: string | null; // user override of the channel link
extra_links: string[]; // extra reference URLs (beyond source_url)
job_kind?: DownloadJobKind; // "download" (default) | "edit" (a per-user trim/crop derivative)
job_kind?: DownloadJobKind; // see DownloadJobKind; absent reads as "download"
source_job_id?: number | null; // parent download an edit was cut from
edit_spec?: EditSpec | null;
can_download: boolean;
+20 -1
View File
@@ -6,7 +6,7 @@
// must still say the category, not print a key.
import { describe, expect, it } from "vitest";
import i18n from "../i18n";
import { failCopyKeys } from "./downloadFailure";
import { failCopyKeys, failTitleKey } from "./downloadFailure";
describe("failCopyKeys", () => {
it("puts a catalog entry before its category, and is the category alone without one", () => {
@@ -47,3 +47,22 @@ describe("failCopyKeys", () => {
);
});
});
describe("failTitleKey", () => {
it("titles an upload's failure as a conversion, anything else as a download", async () => {
await i18n.changeLanguage("en");
// the locale has the copy (a missing key would come back as the key itself)
expect(i18n.t(failTitleKey({ job_kind: "upload" }))).not.toMatch(/^downloads\./);
expect(failTitleKey({ job_kind: "upload" })).toBe("downloads.errors.fail.titleUpload");
expect(failTitleKey({ job_kind: "edit" })).toBe("downloads.errors.fail.title");
expect(failTitleKey({})).toBe("downloads.errors.fail.title");
});
it("an upload's own failures read as an upload's", async () => {
await i18n.changeLanguage("en");
for (const code of ["convert_failed", "upload_missing"]) {
const short = i18n.t(failCopyKeys({ error_code: code, error_detail: null }, "short"));
expect(short).not.toBe(i18n.t("downloads.errors.fail.cat.unknown.short"));
}
});
});
+7
View File
@@ -18,3 +18,10 @@ export function failCopyKeys(
...(code === "unknown" ? [] : [category("unknown")]),
];
}
/** The title over a failed job's explanation: an upload's failure is a conversion, not a download. */
export function failTitleKey(job: Pick<DownloadJob, "job_kind">): string {
return job.job_kind === "upload"
? "downloads.errors.fail.titleUpload"
: "downloads.errors.fail.title";
}