From 078194731b79c1d9647541ff29a6e831375ad404 Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sun, 11 Oct 2026 19:37:13 +0200 Subject: [PATCH] =?UTF-8?q?feat(files):=20the=20Downloads=20page=20goes=20?= =?UTF-8?q?=E2=80=94=20the=20tray,=20a=20Storage=20tab=20and=20a=20Downloa?= =?UTF-8?q?ds=20settings=20tab=20take=20its=20place?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The Downloads module, its rail icon and badge are gone. A bookmark, the remembered page and an old history entry that name it land on Files (urlState.resolvePage); `g d` goes to Files too. - Users page: a Storage tab — the disk in numbers, every user's use against their own limit with their file count, every user's downloads; aligned tables, one menu per row (edit / reset quota, open source, copy, the owner's quota). The admin storage rows carry quota + file count, the admin job list its owner. - Settings: a Downloads tab — the download formats in one list (duplicate a built-in, edit or delete your own) and the site sign-ins, moved from Account. - "View" on an added download, a clip or a converting upload opens the tray on that row (unfolded, back if closed; a finished one opens as a file; under a player it opens without taking focus). - The download dialog opens on the format used last. - DataTable: `pager={false}` for a short list, `cardRow` to give a lone "…" its own card line. - Welcome and README speak of Files; a new Files screenshot. --- CLAUDE.md | 2 +- README.md | 7 +- backend/app/routes/downloads.py | 31 +- backend/tests/test_upload_conversion.py | 23 ++ docs/self-hosting.md | 10 +- frontend/public/welcome/downloads.webp | Bin 88098 -> 0 bytes frontend/public/welcome/files.webp | Bin 0 -> 156988 bytes frontend/src/components/AdminUsers.tsx | 7 +- frontend/src/components/BootGate.dom.test.tsx | 14 +- .../src/components/ClipPanel.dom.test.tsx | 7 + frontend/src/components/ClipPanel.tsx | 6 +- .../DownloadCenter.confirm.dom.test.tsx | 157 -------- frontend/src/components/DownloadCenter.tsx | 199 ---------- .../components/DownloadDialog.dom.test.tsx | 101 ++++++ frontend/src/components/DownloadDialog.tsx | 24 +- .../src/components/DownloadTray.dom.test.tsx | 71 +++- ...est.tsx => DownloadTray.menu.dom.test.tsx} | 105 ++++-- frontend/src/components/DownloadTray.tsx | 38 +- frontend/src/components/Files.tsx | 1 + frontend/src/components/LibraryPlayer.tsx | 2 +- frontend/src/components/NavSidebar.tsx | 7 - .../NavigationProvider.dom.test.tsx | 12 + .../src/components/NavigationProvider.tsx | 19 +- .../src/components/ProfileEditor.dom.test.tsx | 139 +++++++ frontend/src/components/ProfileEditor.tsx | 216 ++++++++--- .../SettingsPanel.downloads.dom.test.tsx | 52 +++ frontend/src/components/SettingsPanel.tsx | 38 +- .../src/components/UploadDialog.dom.test.tsx | 13 +- frontend/src/components/UploadDialog.tsx | 7 +- frontend/src/components/Welcome.tsx | 6 +- frontend/src/components/channelColumns.tsx | 1 + .../downloadAdmin.storage.dom.test.tsx | 160 ++++++++ frontend/src/components/downloadAdmin.tsx | 341 +++++++++++++++--- frontend/src/components/downloadModals.tsx | 4 +- .../src/components/downloadRow.dom.test.tsx | 228 +----------- frontend/src/components/downloadRow.tsx | 234 +----------- frontend/src/components/downloadTitle.ts | 9 +- .../listNav.wiring.contract.test.ts | 1 - frontend/src/components/moduleRegistry.tsx | 2 - .../src/components/navigation.dom.test.ts | 14 +- frontend/src/components/navigationInit.ts | 5 +- .../ui/DataTable.cards.dom.test.tsx | 46 +++ .../ui/DataTable.pager.dom.test.tsx | 39 ++ frontend/src/components/ui/DataTable.tsx | 42 ++- .../ui/pressableSelectable.contract.test.ts | 1 - frontend/src/i18n/locales/en/downloads.json | 49 ++- frontend/src/i18n/locales/en/keys.json | 1 - frontend/src/i18n/locales/en/nav.json | 1 - frontend/src/i18n/locales/en/settings.json | 7 +- frontend/src/i18n/locales/en/users.json | 3 +- frontend/src/i18n/locales/en/welcome.json | 8 +- frontend/src/i18n/locales/hu/downloads.json | 49 ++- frontend/src/i18n/locales/hu/keys.json | 1 - frontend/src/i18n/locales/hu/nav.json | 1 - frontend/src/i18n/locales/hu/settings.json | 7 +- frontend/src/i18n/locales/hu/users.json | 3 +- frontend/src/i18n/locales/hu/welcome.json | 8 +- frontend/src/lib/api/downloads.ts | 15 +- frontend/src/lib/downloadQueue.ts | 25 +- frontend/src/lib/format.ts | 6 - frontend/src/lib/keymap.sequence.test.ts | 2 +- frontend/src/lib/keymap.ts | 10 +- frontend/src/lib/modules.ts | 8 - frontend/src/lib/nav.ts | 17 +- frontend/src/lib/pageKeys.dom.test.tsx | 11 +- frontend/src/lib/pageMeta.test.ts | 1 - frontend/src/lib/storage.ts | 3 + frontend/src/lib/tableColumns.ts | 3 + frontend/src/lib/urlState.ts | 20 +- 69 files changed, 1553 insertions(+), 1147 deletions(-) delete mode 100644 frontend/public/welcome/downloads.webp create mode 100644 frontend/public/welcome/files.webp delete mode 100644 frontend/src/components/DownloadCenter.confirm.dom.test.tsx delete mode 100644 frontend/src/components/DownloadCenter.tsx create mode 100644 frontend/src/components/DownloadDialog.dom.test.tsx rename frontend/src/components/{DownloadCenter.menu.dom.test.tsx => DownloadTray.menu.dom.test.tsx} (68%) create mode 100644 frontend/src/components/ProfileEditor.dom.test.tsx create mode 100644 frontend/src/components/SettingsPanel.downloads.dom.test.tsx create mode 100644 frontend/src/components/downloadAdmin.storage.dom.test.tsx create mode 100644 frontend/src/components/ui/DataTable.cards.dom.test.tsx create mode 100644 frontend/src/components/ui/DataTable.pager.dom.test.tsx diff --git a/CLAUDE.md b/CLAUDE.md index ae8fa045..347e1f3a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -5,7 +5,7 @@ be developed that way. This file is the rules of the road: read it before you to loaded into every agent session, so keep it **short and true**. A rule here that has gone stale is worse than no rule — if you change how the code works, change this file in the same commit. -Siftlode is a self-hosted YouTube reader (+ a local Plex browser/player and a download center). It is a +Siftlode is a self-hosted YouTube reader (+ a local Plex browser/player and a files library with downloads). It is a **modular monolith**: one Postgres, one FastAPI process (uvicorn + APScheduler), one worker process (yt-dlp/ffmpeg), one PO-token sidecar. React 19 (with the React Compiler) + react-query frontend, with a router-free navigation provider. diff --git a/README.md b/README.md index 80ab33b7..b5424876 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,11 @@ blocker and SponsorBlock keep working. tags to slice the feed by. - **Playlists with two-way YouTube sync** — build them locally, keep them in sync in both directions. - **In-app player** with resume, plus keyboard/scroll controls. -- **Download Center** — save videos to the server with yt-dlp in a Plex-friendly layout (format - presets, per-user storage quota), trim / crop / split & join them in a built-in editor, play them +- **Files** — one library for everything you keep: paste a link anywhere in the app to download it + with yt-dlp (format presets, per-user storage quota, the running downloads in a tray), upload your + own files, sort them into folders, trim / crop / split & join videos in a built-in editor, play them where you left off, then save to your device, share with another user, or hand out a public watch - link. + link that plays on any phone. - **Plex library** *(optional)* — point Siftlode at your own Plex server to browse and play its movies and shows alongside your feed, with watch state kept in sync. - **Multi-user** with per-user private state, a shared catalog, and a fair daily API-quota guard. diff --git a/backend/app/routes/downloads.py b/backend/app/routes/downloads.py index 053e83c1..45957b36 100644 --- a/backend/app/routes/downloads.py +++ b/backend/app/routes/downloads.py @@ -842,6 +842,7 @@ def admin_list_downloads( out = [] for j in jobs: data = serialize(j, assets.get(j.asset_id) if j.asset_id is not None else None) + data["user_id"] = j.user_id data["user_email"] = emails.get(j.user_id) out.append(data) return out @@ -860,19 +861,35 @@ def admin_storage(admin: User = Depends(admin_user), db: Session = Depends(get_d .where(DownloadJob.asset_id.is_not(None)) .group_by(DownloadJob.user_id) ).all() - emails = { - u.id: u.email - for u in db.execute(select(User).where(User.id.in_([uid for uid, _ in per_user]))).scalars() + uids = [uid for uid, _ in per_user] + emails = {u.id: u.email for u in db.execute(select(User).where(User.id.in_(uids))).scalars()} + # What each one's Files lists: their finished files. + files: dict[int, int] = { + uid: n + for uid, n in db.execute( + select(DownloadJob.user_id, func.count()) + .where(DownloadJob.user_id.in_(uids), DownloadJob.status == JobStatus.DONE) + .group_by(DownloadJob.user_id) + ).tuples() } + + def row(uid: int) -> dict: + lim = quota.resolve(db, uid) + return { + "user_id": uid, + "email": emails.get(uid), + "footprint_bytes": quota.footprint(db, uid), + "files": files.get(uid, 0), + "max_bytes": lim.max_bytes, + "unlimited": lim.unlimited, + } + return { "files_on_disk": on_disk[0], "total_bytes": int(on_disk[1]), "total_assets": total_assets, "total_cap_bytes": sysconfig.get_int(db, "download_total_max_bytes"), - "per_user": [ - {"user_id": uid, "email": emails.get(uid), "footprint_bytes": quota.footprint(db, uid)} - for uid, _ in per_user - ], + "per_user": [row(uid) for uid in uids], } diff --git a/backend/tests/test_upload_conversion.py b/backend/tests/test_upload_conversion.py index 969c33c0..b3d7bc48 100644 --- a/backend/tests/test_upload_conversion.py +++ b/backend/tests/test_upload_conversion.py @@ -598,6 +598,29 @@ def test_the_admin_storage_view_counts_a_waiting_upload(db, as_user, make_user, assert {p["user_id"]: p["footprint_bytes"] for p in r["per_user"]} == {u.id: 10} +def test_the_admin_storage_view_names_each_users_quota_and_files( + db, as_user, make_user, _root, monkeypatch +): + # A row says how full the user is against THEIR limit (a custom one, here), and how many files + # their Files lists — the finished ones, not one still waiting for its conversion. + _probe(monkeypatch) + u = make_user() + done, _ = _ingest(db, _root, u.id, size=10) + waiting, _ = _ingest(db, _root, u.id, name="b.mov", size=5) + done.status, waiting.status = "done", "queued" + db.commit() + admin = as_user(make_user(role="admin")) + put = admin.put(f"/api/admin/downloads/quota/{u.id}", json={"max_bytes": 1000}) + assert put.status_code == 200 + row = admin.get("/api/admin/downloads/storage").json()["per_user"] + assert [(p["user_id"], p["files"], p["max_bytes"], p["unlimited"]) for p in row] == [ + (u.id, 1, 1000, False) + ] + # Every download names its owner, so its row can open THEIR quota. + jobs = admin.get("/api/admin/downloads").json() + assert {(j["id"], j["user_id"]) for j in jobs} == {(done.id, u.id), (waiting.id, u.id)} + + @pytest.mark.parametrize( ("job_status", "asset_status", "ref_count"), [ diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 1cb68b4d..c253daa3 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -8,7 +8,7 @@ configured in a web wizard on first start. There's no editing of config files by - A machine with **Docker** and the **Docker Compose plugin** (Docker Desktop on Windows/macOS, or Docker Engine on Linux). -- A few hundred MB of disk and ~1 GB RAM free for the app itself. The optional Download Center +- A few hundred MB of disk and ~1 GB RAM free for the app itself. The Files library stores media too — budget disk for whatever you download (it's bounded by per-user quotas you set as admin). - Optional: a domain name + reverse proxy if you want HTTPS / public access (see below). @@ -97,13 +97,13 @@ Only that peer is trusted, so a request sent straight to port `8080` (bypassing app is exposed directly with no proxy. Make sure your proxy actually sets `X-Forwarded-For` (Caddy's `reverse_proxy` and Nginx's `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for` both do). -## Download Center (media storage) +## Files (media storage) -The stack includes a **Download Center**: an admin-enabled feature that saves videos to the server +The stack includes **Files**: one library that saves videos to the server with yt-dlp (Plex-friendly folders + `.nfo`/poster), lets users trim/crop/join clips, and shares them. It runs two extra containers that come up automatically — a `worker` (the download/edit job loop) and a small `bgutil-pot` sidecar (mints YouTube tokens so downloads aren't bot-blocked). No -configuration is required; per-user storage quotas are set on the admin **Downloads → System** page. +configuration is required; per-user storage quotas are set on the admin **Users → Storage** tab. By default the media lives in a Docker-managed volume (`siftlode_files`). To keep it somewhere you can reach from other apps — e.g. a folder your **Plex** server indexes — point it at a host @@ -164,7 +164,7 @@ then cannot read its own secrets. token and every stored refresh token are encrypted with it *inside* the database. Restore a dump against a different key and those values are lost — you would have to clear the affected rows from `system_config` and re-enter every secret by hand. Keep `.env` beside the dump, and just as private. -3. **Downloaded media** *(only if you use the Download Center)* — the `siftlode_files` volume, or +3. **Downloaded media** *(only if you use Files)* — the `siftlode_files` volume, or whatever `FILES_HOST_PATH` points at. Not needed to bring the instance back; the jobs and their metadata are in the database, the files themselves are not. diff --git a/frontend/public/welcome/downloads.webp b/frontend/public/welcome/downloads.webp deleted file mode 100644 index 120ffdc76a8b69e998ab8e43e9cb594d84219297..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 88098 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 Qr>>1=j08<0wqyVR09Evn+a diff --git a/frontend/public/welcome/files.webp b/frontend/public/welcome/files.webp new file mode 100644 index 0000000000000000000000000000000000000000..9f6a22db29a5dd2a03879e16e3a46183d82ab44d GIT binary patch literal 156988 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 z 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�pU8{;ra%>x7 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%0DO1V=*B7`wpdI6Kvt=VOSfhXz;1fo1anj` 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

dABU2V3QlG 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 d@7v#>cFpTVsD_bK(cqZA5aNofK x.id === tab) ? tab : "roles"; return ( @@ -99,6 +103,7 @@ export default function AdminUsers() { )} + {active === "storage" && } ); diff --git a/frontend/src/components/BootGate.dom.test.tsx b/frontend/src/components/BootGate.dom.test.tsx index e436e12c..e281c054 100644 --- a/frontend/src/components/BootGate.dom.test.tsx +++ b/frontend/src/components/BootGate.dom.test.tsx @@ -70,13 +70,13 @@ afterEach(() => { describe("BootGate", () => { it("a new tab with Startup off opens on the page the account left — never on Home first", async () => { localStorage.setItem("siftlode.startOnHome.5", "0"); - localStorage.setItem("siftlode.page.5", "downloads"); + localStorage.setItem("siftlode.page.5", "plex"); vi.spyOn(api, "me").mockResolvedValue(account()); mount(); - expect((await screen.findByTestId("app")).textContent).toBe("downloads"); - expect(renders.map((r) => r.page)).toEqual(renders.map(() => "downloads")); - expect(new URLSearchParams(window.location.search).get("page")).toBe("downloads"); + expect((await screen.findByTestId("app")).textContent).toBe("plex"); + expect(renders.map((r) => r.page)).toEqual(renders.map(() => "plex")); + expect(new URLSearchParams(window.location.search).get("page")).toBe("plex"); expect(sessionStorage.getItem(ACTIVE_ACCOUNT_KEY)).toBe("5"); // the tab is pinned }); @@ -176,12 +176,12 @@ describe("BootGate", () => { // Pinned to 9, but 9 was signed out in another tab: the server answers as the browser's default, 5. sessionStorage.setItem(ACTIVE_ACCOUNT_KEY, "9"); localStorage.setItem("siftlode.startOnHome.5", "0"); - localStorage.setItem("siftlode.page.5", "downloads"); + localStorage.setItem("siftlode.page.5", "plex"); vi.spyOn(api, "me").mockResolvedValue(account()); mount(); - expect((await screen.findByTestId("app")).textContent).toBe("downloads"); - expect(renders.map((r) => r.page)).toEqual(renders.map(() => "downloads")); + expect((await screen.findByTestId("app")).textContent).toBe("plex"); + expect(renders.map((r) => r.page)).toEqual(renders.map(() => "plex")); expect(sessionStorage.getItem(ACTIVE_ACCOUNT_KEY)).toBe("5"); }); diff --git a/frontend/src/components/ClipPanel.dom.test.tsx b/frontend/src/components/ClipPanel.dom.test.tsx index 4f2a6ba0..0665b4cb 100644 --- a/frontend/src/components/ClipPanel.dom.test.tsx +++ b/frontend/src/components/ClipPanel.dom.test.tsx @@ -25,6 +25,8 @@ vi.mock("../lib/notifications", async (importOriginal) => { const actual = await importOriginal(); return { ...actual, notify: (n: unknown) => notify(n) }; }); +const showInTray = vi.fn(); +vi.mock("../lib/downloadQueue", () => ({ showInTray: (id: number) => showInTray(id) })); const notifyActionError = vi.fn(); vi.mock("../lib/actionErrors", async (importOriginal) => { const actual = await importOriginal(); @@ -188,6 +190,11 @@ describe("ClipPanel", () => { expect(notify).toHaveBeenCalledWith( expect.objectContaining({ level: "success", message: "clip.queued" }), ); + // Its View opens the tray on the new clip. + ( + notify.mock.calls[notify.mock.calls.length - 1]![0] as { action: { onClick: () => void } } + ).action.onClick(); + expect(showInTray).toHaveBeenCalledWith(1); }); it("sends no audio track for a YouTube clip", async () => { diff --git a/frontend/src/components/ClipPanel.tsx b/frontend/src/components/ClipPanel.tsx index 6dad8438..f92194be 100644 --- a/frontend/src/components/ClipPanel.tsx +++ b/frontend/src/components/ClipPanel.tsx @@ -7,7 +7,7 @@ import { DISPLAY_NAME_MAX } from "../lib/api/downloads"; import { invalidateDownloads } from "../lib/downloads"; import { notify } from "../lib/notifications"; import { notifyActionError } from "../lib/actionErrors"; -import { navigateTo } from "../lib/nav"; +import { showInTray } from "../lib/downloadQueue"; import { log } from "../lib/log"; import { formatTimecode } from "../lib/timecode"; import { handleBounds, markIn, markOut, zoomWindow, type ClipRange } from "../lib/clipRange"; @@ -192,13 +192,13 @@ export default function ClipPanel({ if (busy) return; setBusy(true); try { - await api.enqueueClip(body); + const job = await api.enqueueClip(body); invalidateDownloads(qc); log.info("clip.queued", "clip queued", { source: src.source, seconds: Math.round(length) }); notify({ level: "success", message: t("clip.queued"), - action: { label: t("clip.view"), onClick: () => navigateTo("downloads") }, + action: { label: t("clip.view"), onClick: () => showInTray(job.id) }, }); onClose(); } catch (err) { diff --git a/frontend/src/components/DownloadCenter.confirm.dom.test.tsx b/frontend/src/components/DownloadCenter.confirm.dom.test.tsx deleted file mode 100644 index d216f11f..00000000 --- a/frontend/src/components/DownloadCenter.confirm.dom.test.tsx +++ /dev/null @@ -1,157 +0,0 @@ -// @vitest-environment jsdom -// -// The Download Center's confirm dialogs name their ACTION on the confirm button: the shared -// `confirmThen` once labelled it with the dialog's question — the cancel dialog's own "Cancel" beside -// "Cancel". A finished file's confirms are the Files page's (Files.menu.dom.test.tsx). i18n is -// key-passthrough, so labels read as keys. -import { afterEach, describe, expect, it, vi } from "vitest"; -import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import type { DownloadJob } from "../lib/api"; - -const mocks = vi.hoisted(() => ({ - jobs: [] as unknown[], - confirm: vi.fn( - async (_opts: { title: string; message?: string; confirmLabel?: string }) => false, - ), -})); - -vi.mock("react-i18next", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), - }; -}); -vi.mock("./confirmContext", () => ({ useConfirm: () => mocks.confirm })); -vi.mock("../lib/useLiveQuery", () => ({ - useLiveQuery: (key: readonly unknown[]) => ({ - data: key[0] === "downloads" ? mocks.jobs : undefined, - isError: false, - isLoading: false, - refetch: vi.fn(), - }), -})); -vi.mock("../lib/useMe", () => ({ useMe: () => ({ role: "user" }) })); -// The page toolbar (title + tabs) renders into the app shell's slot, which a bare render has none of. -vi.mock("./ui/PageShell", async (importOriginal) => ({ - ...(await importOriginal()), - PageToolbar: ({ children }: { children: React.ReactNode }) => <>{children}, -})); - -import DownloadCenter from "./DownloadCenter"; - -const job = (id: number, status: DownloadJob["status"]): DownloadJob => ({ - id, - status, - progress: 0, - speed_bps: null, - eta_s: null, - phase: null, - error: null, - error_code: null, - error_detail: null, - queue_pos: 0, - created_at: null, - source_kind: "youtube", - source_ref: `v${id}`, - source_url: null, - poster_url: null, - profile_id: null, - spec: { - mode: "av", - max_height: null, - container: null, - audio_format: null, - vcodec: null, - embed_subs: false, - embed_chapters: false, - embed_thumbnail: false, - sponsorblock: false, - }, - display_name: `Clip ${id}`, - display_uploader: null, - display_uploader_url: null, - extra_links: [], - can_download: status === "done", - folder_id: null, - kept: false, - expires_at: null, - asset: null, -}); - -afterEach(() => { - cleanup(); - mocks.confirm.mockClear(); -}); - -/** Pick `label` from the row's menu, opened by its "…". The surface is visibility:hidden in jsdom - * (floating-ui never positions it), so the row is matched on its text. */ -async function pick(row: string, label: string) { - const more = screen - .getByText(row) - .closest("[data-nav-index]")! - .querySelector('button[aria-label="downloads.rowMenu"]')!; - fireEvent.click(more); - const item = await waitFor(() => { - const found = screen - .getAllByRole("menuitem", { hidden: true }) - .find((b) => b.textContent === label); - expect(found).toBeTruthy(); - return found!; - }); - fireEvent.click(item); -} - -describe("DownloadCenter confirm dialogs", () => { - it("label the confirm button with the action, not the dialog's question", async () => { - mocks.jobs = [job(1, "running")]; - render( - - - , - ); - - // The destructive and rare row buttons live in the row's "…". - await pick("Clip 1", "downloads.actions.cancel"); - await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); - - expect(mocks.confirm.mock.calls.map(([o]) => [o.title, o.confirmLabel])).toEqual([ - ["downloads.confirm.cancelTitle", "downloads.confirm.cancelAction"], - ]); - }); - - it("cancelling an upload says the uploaded file goes with it", async () => { - // Nothing to fetch again: the cancel deletes the original from the server. - mocks.jobs = [{ ...job(3, "queued"), job_kind: "upload" }]; - render( - - - , - ); - await pick("Clip 3", "downloads.actions.cancel"); - await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); - const [opts] = mocks.confirm.mock.calls[0]!; - expect([opts.title, opts.confirmLabel]).toEqual([ - "downloads.confirm.cancelUploadTitle", - "downloads.confirm.cancelUploadAction", - ]); - }); - - it("cancelling a clip speaks of the clip", async () => { - mocks.jobs = [{ ...job(4, "running"), job_kind: "edit" }]; - render( - - - , - ); - await pick("Clip 4", "downloads.actions.cancel"); - await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); - const [opts] = mocks.confirm.mock.calls[0]!; - expect([opts.title, opts.message, opts.confirmLabel]).toEqual([ - "downloads.confirm.cancelEditTitle", - "downloads.confirm.cancelEditBody", - "downloads.confirm.cancelEditAction", - ]); - }); -}); diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx deleted file mode 100644 index de372660..00000000 --- a/frontend/src/components/DownloadCenter.tsx +++ /dev/null @@ -1,199 +0,0 @@ -import { lazy, Suspense, useState } from "react"; -import { qk } from "../lib/queryKeys"; -import { useTranslation } from "react-i18next"; -import { Pause, Play, RefreshCw, Settings2, UploadCloud } from "lucide-react"; -import Tabs from "./ui/Tabs"; -import { useTabPanel } from "./ui/tabPanel"; -import { PageToolbar } from "./ui/PageShell"; -import { LoadingState, StateMessage } from "./ui/QueryState"; -// Lazy: these dialogs open on demand, so they stay out of the Downloads page chunk until used. -const UploadDialog = lazy(() => import("./UploadDialog")); -const ProfileEditor = lazy(() => import("./ProfileEditor")); -import { useLiveQuery } from "../lib/useLiveQuery"; -import { useMe } from "../lib/useMe"; -import { type DownloadJob } from "../lib/api"; -import { fetchQueue } from "../lib/downloadQueue"; -import { Button } from "./ui/Button"; -import { DownloadRow, IconBtn } from "./downloadRow"; -import { ErrorModal } from "./downloadModals"; -import { AdminSystem } from "./downloadAdmin"; -import NavGrid from "./ui/NavGrid"; -import { useContextMenu } from "./ui/useContextMenu"; -import { ContextMenu } from "./ui/ContextMenu"; -import { MoreButton } from "./ui/MoreButton"; -import { useDownloadActions } from "./useDownloadActions"; -import { LinkBox } from "./LinkBox"; - -const jobKey = (job: DownloadJob) => job.id; - -// The download queue: what is downloading, waiting or failed, the link box and the upload, the -// download profiles, and (admin) the instance's downloads. A finished download is a library file and -// lives in Files. -export default function DownloadCenter() { - const me = useMe(); - const { t } = useTranslation(); - // Always land on the queue tab (not a persisted last-tab) — the queue is where new work and - // any failures are, so it's the useful default every visit. - const [tab, setTab] = useState("queue"); - const panel = useTabPanel(); - const [upload, setUpload] = useState(false); - const [manage, setManage] = useState(false); - const [errorJob, setErrorJob] = useState(null); - - // Poll fast (2s) while work is actually in flight; back off to 15s once the queue is settled - // (only done/error/paused rows left, which don't advance on their own) so an idle Download Center - // isn't hitting the API — and adding to the shared box's load — every 2s for nothing. - const jobsQ = useLiveQuery(qk.downloads(), fetchQueue, { - intervalMs: (data) => - data?.some((j) => j.status === "queued" || j.status === "running") ? 2000 : 15000, - }); - const jobs = jobsQ.data ?? []; - const queue = jobs.filter((j) => ["queued", "running", "paused", "error"].includes(j.status)); - - // --- The rows' ONE menu ------------------------------------------------------------- - // Keyed by the job's id: the queue polls every 2 s while a job runs, replacing the object under an - // open menu, and its "…" must still close it. - const rowMenu = useContextMenu({ keyOf: jobKey }); - const actions = useDownloadActions(); - const menuItems = rowMenu.target && tab === "queue" ? actions.items(rowMenu.target) : []; - // The row's menu triggers and its "…". - const rowMenuProps = (job: DownloadJob) => { - const mine = rowMenu.open && rowMenu.target?.id === job.id; - return { - rowProps: rowMenu.triggerProps(job), - more: ( - - ), - }; - }; - - const tabs = [ - { id: "queue", label: t("downloads.tabs.queue"), badge: queue.length }, - ...(me.role === "admin" ? [{ id: "system", label: t("downloads.tabs.system") }] : []), - ]; - // Guard against the active tab going away under us (e.g. admin on "system" gets demoted mid-session) - // — that would render a blank page with no active tab. Fall back to the default. - // Adjusted during render rather than from an effect: as an effect the blank page it exists to - // prevent was committed and painted first. `setTab` is this component's own state (persisted per - // account), so a render-phase write is allowed, and it converges — "queue" is always in the list. - if (!tabs.some((tb) => tb.id === tab)) setTab("queue"); - - return ( - <> - {/* The tabs are the page's band (B4); the tab content scrolls under them. No visible title: the - nav rail and the tab title name the page, the shell's hidden h1 names it to a screen reader. */} - -

- - -
- - -
- {tab === "queue" && ( - <> - {/* The field takes the whole line when the buttons would squeeze it (at 375 it had 57px). */} -
- - {/* Local upload is a peer to "add a URL": a general file → the same library. */} - -
- - {queue.map((job, i) => ( - - {/* The queue's primary action stays in sight; cancel moved into the "…". */} - {job.status === "queued" || job.status === "running" ? ( - actions.run(job, "pause")} - title={t("downloads.actions.pause")} - > - - - ) : ( - actions.run(job, "resume")} - title={t( - job.status === "error" - ? "downloads.actions.retry" - : "downloads.actions.resume", - )} - > - {/* Retry is "do it again", not playback: the Play glyph promised a video. */} - {job.status === "error" ? ( - - ) : ( - - )} - - )} - - ))} - {queue.length === 0 && - (jobsQ.isError && !jobsQ.data ? ( - jobsQ.refetch()}> - {t("common.loadError")} - - ) : jobsQ.isLoading ? ( - - ) : ( -
- {t("downloads.empty.queue")} -
- ))} -
- - )} - - {tab === "system" && me.role === "admin" && } - - - - - {upload && setUpload(false)} />} - {manage && setManage(false)} />} - {errorJob && ( - actions.run(errorJob, "resume")} - onClose={() => setErrorJob(null)} - /> - )} - -
- - ); -} diff --git a/frontend/src/components/DownloadDialog.dom.test.tsx b/frontend/src/components/DownloadDialog.dom.test.tsx new file mode 100644 index 00000000..119e2100 --- /dev/null +++ b/frontend/src/components/DownloadDialog.dom.test.tsx @@ -0,0 +1,101 @@ +// @vitest-environment jsdom +// +// The download dialog opens on the format used last (per account), so a pasted link is one Enter +// away; a format deleted since falls back to the first. Its "added" notice's View opens the tray on +// the new job. The prefs are a map that outlives a mount — a reload, as far as the dialog can tell. +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useState } from "react"; +import { api, type DownloadProfile } from "../lib/api"; + +const h = vi.hoisted(() => ({ + prefs: new Map(), + notify: vi.fn(), + showInTray: vi.fn(), +})); + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), +})); +vi.mock("../lib/usePref", () => ({ + usePref: (key: string, initial: T) => { + const [value, setValue] = useState(() => + h.prefs.has(key) ? (h.prefs.get(key) as T) : initial, + ); + return [ + value, + (next: T) => { + h.prefs.set(key, next); + setValue(next); + }, + ]; + }, +})); +vi.mock("../lib/notifications", async (importOriginal) => ({ + ...(await importOriginal()), + notify: h.notify, +})); +vi.mock("../lib/downloadQueue", () => ({ showInTray: h.showInTray })); + +import DownloadDialog from "./DownloadDialog"; + +const profile = (id: number, name: string) => + ({ id, name, is_builtin: false, spec: { mode: "av" } }) as unknown as DownloadProfile; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + h.prefs.clear(); + h.notify.mockClear(); + h.showInTray.mockClear(); +}); + +function open() { + vi.spyOn(api, "downloadProfiles").mockResolvedValue([profile(1, "Best"), profile(2, "Phone")]); + const enqueue = vi.spyOn(api, "enqueueDownload").mockResolvedValue({ id: 77 } as never); + render( + + {}} /> + , + ); + return enqueue; +} + +const picked = () => + screen.getByRole("button", { name: /^downloads\.dialog\.profile: / }).getAttribute("aria-label"); + +describe("DownloadDialog", () => { + it("opens on the format used last; a deleted one falls back to the first", async () => { + const enqueue = open(); + await waitFor(() => expect(picked()).toBe("downloads.dialog.profile: Best")); + fireEvent.click(screen.getByRole("button", { name: /^downloads\.dialog\.profile: / })); + const phone = (await screen.findAllByRole("menuitemradio", { hidden: true })).find( + (o) => o.textContent === "Phone", + )!; + fireEvent.click(phone); + fireEvent.click(screen.getByText("downloads.dialog.submit")); + await waitFor(() => + expect(enqueue).toHaveBeenCalledWith(expect.objectContaining({ profile_id: 2 })), + ); + cleanup(); + + open(); + await waitFor(() => expect(picked()).toBe("downloads.dialog.profile: Phone")); + cleanup(); + + h.prefs.set("downloadProfile", 99); + open(); + await waitFor(() => expect(picked()).toBe("downloads.dialog.profile: Best")); + }); + + it("its notice's View opens the tray on the new job", async () => { + open(); + await waitFor(() => expect(picked()).toBe("downloads.dialog.profile: Best")); + fireEvent.click(screen.getByText("downloads.dialog.submit")); + await waitFor(() => expect(h.notify).toHaveBeenCalledTimes(1)); + (h.notify.mock.calls[0]![0] as { action: { onClick: () => void } }).action.onClick(); + expect(h.showInTray).toHaveBeenCalledWith(77); + }); +}); diff --git a/frontend/src/components/DownloadDialog.tsx b/frontend/src/components/DownloadDialog.tsx index bf09fd3e..4b7e7d2b 100644 --- a/frontend/src/components/DownloadDialog.tsx +++ b/frontend/src/components/DownloadDialog.tsx @@ -11,13 +11,18 @@ import { invalidateDownloads } from "../lib/downloads"; const ProfileEditor = lazy(() => import("./ProfileEditor")); import { notify } from "../lib/notifications"; import { notifyActionError } from "../lib/actionErrors"; -import { navigateTo } from "../lib/nav"; +import { showInTray } from "../lib/downloadQueue"; import { profileName } from "../lib/vocab"; +import { LS } from "../lib/storage"; +import { usePref } from "../lib/usePref"; import FilterSelect from "./ui/FilterSelect"; import { Input } from "./ui/Input"; -// Preset picker shown when the user hits Download on a card / in the player, or adds a URL from -// the Download Center. Enqueues via the API and points the user at the Download Center. +// Preset picker shown when the user hits Download on a card / in the player, or brings a link (the +// Files link box, a paste anywhere). Enqueues via the API; its notice's "View" opens the tray on it. +// It opens on the format used last (per account): a pasted link is then one Enter away. +const parseProfile = (v: unknown): number | null => (typeof v === "number" ? v : null); + export default function DownloadDialog({ source, title, @@ -36,23 +41,30 @@ export default function DownloadDialog({ const [manage, setManage] = useState(false); const profiles = profilesQ.data ?? []; - const chosen = profileId ?? profiles[0]?.id ?? null; + const [lastProfile, setLastProfile] = usePref("downloadProfile", null, { + localKey: LS.downloadProfile, + parse: parseProfile, + }); + // The one used last, while it still exists (a deleted format falls back to the first). + const remembered = profiles.some((p) => p.id === lastProfile) ? lastProfile : null; + const chosen = profileId ?? remembered ?? profiles[0]?.id ?? null; const submit = async () => { if (!chosen || busy) return; setBusy(true); const displayName = name.trim() || undefined; try { - await api.enqueueDownload({ + const job = await api.enqueueDownload({ source, profile_id: chosen, display_name: displayName, }); invalidateDownloads(qc); + setLastProfile(chosen); notify({ level: "success", message: t("downloads.dialog.added"), - action: { label: t("downloads.dialog.view"), onClick: () => navigateTo("downloads") }, + action: { label: t("downloads.dialog.view"), onClick: () => showInTray(job.id) }, }); onClose(); } catch (e) { diff --git a/frontend/src/components/DownloadTray.dom.test.tsx b/frontend/src/components/DownloadTray.dom.test.tsx index fe62ddb2..334fb7b5 100644 --- a/frontend/src/components/DownloadTray.dom.test.tsx +++ b/frontend/src/components/DownloadTray.dom.test.tsx @@ -12,6 +12,7 @@ import { api, type QueueJob } from "../lib/api"; const h = vi.hoisted(() => ({ jobs: [] as unknown[], + updatedAt: 0, openFile: vi.fn(), share: vi.fn(async () => "https://x/watch/t"), notify: vi.fn(), @@ -27,7 +28,13 @@ vi.mock("react-i18next", async (importOriginal) => ({ }), })); vi.mock("../lib/useLiveQuery", () => ({ - useLiveQuery: () => ({ data: h.jobs, isError: false, isLoading: false, refetch: vi.fn() }), + useLiveQuery: () => ({ + data: h.jobs, + dataUpdatedAt: h.updatedAt, + isError: false, + isLoading: false, + refetch: vi.fn(), + }), })); // The prefs as state over a map that outlives a mount — a reload, as far as the tray can tell. How // usePref stores them is its own business (usePref.dom.test). @@ -57,11 +64,13 @@ vi.mock("../lib/notifications", async (importOriginal) => ({ })); import DownloadTray from "./DownloadTray"; -import { noteQueue, resetQueueWatch } from "../lib/downloadQueue"; +import { noteQueue, resetQueueWatch, showInTray } from "../lib/downloadQueue"; +import { pushLayer, removeLayer } from "../lib/layerStack"; beforeEach(() => { resetQueueWatch(); h.prefs.clear(); + h.updatedAt = 0; globalThis.ResizeObserver ??= class { observe() {} unobserve() {} @@ -265,4 +274,62 @@ describe("DownloadTray", () => { fireEvent.click(screen.getByRole("button", { name: "tray.close" })); expect(document.activeElement?.id).toBe("content"); }); + + describe("View on a job just started", () => { + it("brings a closed tray back, on the job's row", async () => { + poll([job(3, "error")]); + const view = mount(); + fireEvent.click(screen.getByRole("button", { name: "tray.close" })); + expect(tray()).toBeNull(); + act(() => showInTray(3)); + view.rerender(); + expect(tray()).not.toBeNull(); + await waitFor(() => expect(document.activeElement).toBe(rowOf("Clip 3"))); + }); + + it("unfolds a folded tray onto the row", async () => { + poll([job(1, "running"), job(2, "queued")]); + mount(); + fireEvent.click(screen.getByRole("button", { name: "tray.fold" })); + act(() => showInTray(2)); + await waitFor(() => expect(document.activeElement).toBe(rowOf("Clip 2"))); + }); + + it("asked from inside a player, it opens under it but leaves the keyboard there", async () => { + poll([job(1, "running"), job(2, "queued")]); + mount(); + fireEvent.click(screen.getByRole("button", { name: "tray.fold" })); + const player = document.createElement("button"); + document.body.append(player); + player.focus(); + const layer = pushLayer(); + act(() => showInTray(2)); + await waitFor(() => expect(screen.getByText("Clip 2")).toBeTruthy()); + expect(document.activeElement).toBe(player); + removeLayer(layer); + player.remove(); + }); + + it("a job done before the tray saw it run opens as a file", () => { + // The queue's first look only learns what is there: this one never ran in this tab. + poll([job(5, "done", { folder_id: 7 })]); + mount(); + act(() => showInTray(5)); + expect(h.openFile).toHaveBeenCalledWith(5, 7); + expect(tray()).toBeNull(); + }); + + it("a job a later poll does not have is let go — nothing opens, nothing waits for it", () => { + poll([job(1, "running")]); + const view = mount(); + fireEvent.click(screen.getByRole("button", { name: "tray.fold" })); + act(() => showInTray(9)); + h.updatedAt = Date.now() + 1000; + poll([job(1, "running")], view); + // Had the ask stayed, the job turning up later would pull the tray open on it. + poll([job(1, "running"), job(9, "running")], view); + expect(screen.queryByText("Clip 9")).toBeNull(); + expect(h.openFile).not.toHaveBeenCalled(); + }); + }); }); diff --git a/frontend/src/components/DownloadCenter.menu.dom.test.tsx b/frontend/src/components/DownloadTray.menu.dom.test.tsx similarity index 68% rename from frontend/src/components/DownloadCenter.menu.dom.test.tsx rename to frontend/src/components/DownloadTray.menu.dom.test.tsx index c85cb948..a28411e3 100644 --- a/frontend/src/components/DownloadCenter.menu.dom.test.tsx +++ b/frontend/src/components/DownloadTray.menu.dom.test.tsx @@ -1,18 +1,24 @@ // @vitest-environment jsdom // -// The Download Center's queue: a row keeps its primary action (pause / resume / retry) in sight, and -// the copies and cancel are its ONE menu, opened by the row's "…" (it shows on hover or focus) or a -// right-click on the row. A finished download's row is the Files page's (Files.menu.dom.test.tsx). +// The tray's row menu: the copies and cancel of a waiting, running or failed job are its ONE menu, +// opened by the row's "…" (it shows on hover or focus) or a right-click on the row; a failed job's +// first row is Retry. The cancel confirm names its ACTION on its button, and speaks of what is +// cancelled (a download, an upload, a clip). The tray's own showing and done rows are +// DownloadTray.dom.test.tsx's; a finished file's menu is the Files page's (Files.menu.dom.test.tsx). // i18n is key-passthrough, so labels read as keys. import { afterEach, describe, expect, it, vi } from "vitest"; import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useState } from "react"; import { api, type DownloadJob } from "../lib/api"; import { menuGroups } from "./ui/menuGroups"; const mocks = vi.hoisted(() => ({ jobs: [] as unknown[], - confirm: vi.fn(async (_o?: unknown) => true), + confirm: vi.fn( + async (_o?: { title: string; message?: string; confirmLabel?: string; danger?: boolean }) => + true, + ), })); vi.mock("react-i18next", async (importOriginal) => { @@ -31,13 +37,18 @@ vi.mock("../lib/useLiveQuery", () => ({ refetch: vi.fn(), }), })); -vi.mock("../lib/useMe", () => ({ useMe: () => ({ role: "user" }) })); -vi.mock("./ui/PageShell", async (importOriginal) => ({ - ...(await importOriginal()), - PageToolbar: ({ children }: { children: React.ReactNode }) => <>{children}, +vi.mock("../lib/usePref", () => ({ + usePref: (_key: string, initial: T) => useState(initial), })); +vi.mock("./filesContext", () => ({ useFilesActions: () => ({ openFile: vi.fn() }) })); -import DownloadCenter from "./DownloadCenter"; +globalThis.ResizeObserver ??= class { + observe() {} + unobserve() {} + disconnect() {} +} as unknown as typeof ResizeObserver; + +import DownloadTray from "./DownloadTray"; const job = ( id: number, @@ -68,6 +79,7 @@ const job = ( extra_links: [], can_download: status === "done", job_kind: "download", + source_key: "youtube", asset: { media_kind: "video", duration_s: 60 }, ...extra, }) as unknown as DownloadJob; @@ -78,25 +90,23 @@ afterEach(() => { mocks.confirm.mockClear(); }); -function mount(tab?: string) { +function mount() { const qc = new QueryClient(); const tree = () => ( - + ); const { rerender } = render(tree()); mounted.rerender = () => rerender(tree()); - if (tab) - fireEvent.click(screen.getByRole("tab", { name: new RegExp(`downloads\\.tabs\\.${tab}`) })); } -// The last mount's re-render: what a poll's new data does to the page. +// The last mount's re-render: what a poll's new data does to the tray. const mounted = { rerender: () => {} }; const rowOf = (text: string) => screen.getByText(text).closest("[data-nav-index]")!; const moreOf = (text: string) => - rowOf(text).querySelector('button[aria-label="downloads.rowMenu"]')!; + rowOf(text).querySelector('button[aria-label="tray.rowMenu"]')!; const menuRows = () => screen.getAllByRole("menuitem", { hidden: true }).map((b) => b.textContent ?? ""); async function openMenu(open: () => void) { @@ -105,16 +115,10 @@ async function openMenu(open: () => void) { expect(screen.getAllByRole("menuitem", { hidden: true }).length).toBeGreaterThan(0), ); } -const rowButtons = (text: string) => - [...rowOf(text).querySelectorAll("button, a[aria-label]")].map((b) => - b.getAttribute("aria-label"), - ); - -describe("DownloadCenter · the queue row", () => { - it("shows pause and its '…' only; the '…' hides until hover or focus", async () => { +describe("DownloadTray · the row menu", () => { + it("the '…' hides until hover or focus", () => { mocks.jobs = [job(1, "running")]; mount(); - expect(rowButtons("Clip 1")).toEqual(["downloads.actions.pause", "downloads.rowMenu"]); expect(moreOf("Clip 1").classList.contains("reveal")).toBe(true); expect(rowOf("Clip 1").classList.contains("group")).toBe(true); }); @@ -139,15 +143,10 @@ describe("DownloadCenter · the queue row", () => { ]); }); - it("draws Retry as 'do it again', never the Play glyph (row and menu)", async () => { - mocks.jobs = [job(1, "error"), job(2, "paused")]; + it("draws Retry as 'do it again', never the Play glyph", async () => { + mocks.jobs = [job(1, "error")]; mount(); const glyph = (el: Element) => el.querySelector("svg")?.getAttribute("class") ?? ""; - const retry = rowOf("Clip 1").querySelector('[aria-label="downloads.actions.retry"]')!; - expect(glyph(retry)).toContain("lucide-refresh-cw"); - // A paused job resumes: that one keeps the transport glyph. - const resume = rowOf("Clip 2").querySelector('[aria-label="downloads.actions.resume"]')!; - expect(glyph(resume)).toContain("lucide-play"); await openMenu(() => fireEvent.contextMenu(rowOf("Clip 1"))); const row = screen .getAllByRole("menuitem", { hidden: true }) @@ -211,6 +210,52 @@ describe("DownloadCenter · the queue row", () => { }); }); +describe("DownloadTray · the cancel confirm", () => { + const cancelOf = async (title: string) => { + mount(); + await openMenu(() => fireEvent.click(moreOf(title))); + fireEvent.click( + screen + .getAllByRole("menuitem", { hidden: true }) + .find((b) => b.textContent === "downloads.actions.cancel")!, + ); + await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); + return mocks.confirm.mock.calls[0]![0]!; + }; + + it("labels its button with the action, not the dialog's question", async () => { + mocks.confirm.mockResolvedValueOnce(false); + mocks.jobs = [job(1, "running")]; + const o = await cancelOf("Clip 1"); + expect([o.title, o.confirmLabel]).toEqual([ + "downloads.confirm.cancelTitle", + "downloads.confirm.cancelAction", + ]); + }); + + it("cancelling an upload says the uploaded file goes with it", async () => { + // Nothing to fetch again: the cancel deletes the original from the server. + mocks.confirm.mockResolvedValueOnce(false); + mocks.jobs = [job(3, "queued", { job_kind: "upload" })]; + const o = await cancelOf("Clip 3"); + expect([o.title, o.confirmLabel]).toEqual([ + "downloads.confirm.cancelUploadTitle", + "downloads.confirm.cancelUploadAction", + ]); + }); + + it("cancelling a clip speaks of the clip", async () => { + mocks.confirm.mockResolvedValueOnce(false); + mocks.jobs = [job(4, "running", { job_kind: "edit" })]; + const o = await cancelOf("Clip 4"); + expect([o.title, o.message, o.confirmLabel]).toEqual([ + "downloads.confirm.cancelEditTitle", + "downloads.confirm.cancelEditBody", + "downloads.confirm.cancelEditAction", + ]); + }); +}); + describe("menuGroups", () => { const row = (id: string) => ({ id, label: id, onSelect: () => {} }); it("rules between non-empty groups only", () => { diff --git a/frontend/src/components/DownloadTray.tsx b/frontend/src/components/DownloadTray.tsx index 2aa2bc94..dcd75730 100644 --- a/frontend/src/components/DownloadTray.tsx +++ b/frontend/src/components/DownloadTray.tsx @@ -18,12 +18,15 @@ import { failCopyKeys, failTitleKey } from "../lib/downloadFailure"; import { IN_FLIGHT, clearFinished, + doneRevealing, fetchQueue, takeUnannounced, useFinished, + useRevealing, useUnannounced, } from "../lib/downloadQueue"; -import { navItemAttrs } from "../lib/listNav"; +import { anyBlockingLayer } from "../lib/layerStack"; +import { focusNavItem, navItemAttrs } from "../lib/listNav"; import { notify } from "../lib/notifications"; import { qk } from "../lib/queryKeys"; import { LS } from "../lib/storage"; @@ -57,7 +60,8 @@ type Fold = "open" | "folded"; const parseFold = (v: unknown): Fold => (v === "folded" ? "folded" : "open"); const parseClosed = (v: unknown): string | null => (typeof v === "string" ? v : null); -// Byte progress (a bar with a %) vs ffmpeg's post-steps (a pulse): the queue row's split. +// Byte progress (a bar with a %) vs a post-step that reports none (a pulse): "editing" and +// "converting" report a real % from ffmpeg's out_time. const BYTE_PHASES = ["video", "audio", "editing", "converting"]; const keyOf = (job: QueueJob) => job.id; @@ -132,6 +136,35 @@ export default function DownloadTray() { else if (target === "open") foldButton.current?.focus(); }, [fold]); + // --- "View" on a job just started: the tray opens on its row ---------------------------------- + // Answered over as many renders as it takes: a closed or folded tray is brought back first, and the + // row is focused once it is on the page. A job the tray does not list is done already (it opens as + // a file), or — once a poll made after the ask has landed without it — not in the queue at all. + // Asked from inside a player (a clip's notice, the player's own download), the tray opens under it + // but leaves the keyboard there: the row is behind the player, out of the reader's sight. + const box = useRef(null); + const revealing = useRevealing(); + const answerReveal = useEffectEvent(() => { + if (!revealing) return; + const index = rows.findIndex((j) => j.id === revealing.id); + if (index >= 0) { + if (!shown) setClosedAt(null); + else if (fold === "folded") setFold("open"); + else { + if (box.current && !anyBlockingLayer()) focusNavItem(box.current, index); + doneRevealing(); + } + return; + } + const job = queueQ.data?.find((j) => j.id === revealing.id); + if (job?.status === "done") { + doneRevealing(); + openFile(job.id, job.folder_id); + } else if (queueQ.dataUpdatedAt > revealing.at) doneRevealing(); + else void qc.invalidateQueries({ queryKey: qk.downloads() }); + }); + useEffect(() => answerReveal(), [revealing, queueQ.data, shown, fold]); + // --- the done notice ------------------------------------------------------------------------ const announce = useEffectEvent((job: QueueJob) => { const title = jobTitle(job); @@ -153,7 +186,6 @@ export default function DownloadTray() { }, [fresh]); // --- Back to top steps up over the tray while it shows ----------------------------------------- - const box = useRef(null); useLayoutEffect(() => { const el = box.current; if (!el) { diff --git a/frontend/src/components/Files.tsx b/frontend/src/components/Files.tsx index 199adf0d..95711670 100644 --- a/frontend/src/components/Files.tsx +++ b/frontend/src/components/Files.tsx @@ -411,6 +411,7 @@ export default function Files() { width: "10rem", align: "right", cardLabel: false, + cardRow: true, render: (f) => { const a = f.shared ? sharedRowActions(f) : libraryRowActions(f); const mine = rowMenu.open && rowMenu.target?.id === f.id; diff --git a/frontend/src/components/LibraryPlayer.tsx b/frontend/src/components/LibraryPlayer.tsx index 37d26328..031df4af 100644 --- a/frontend/src/components/LibraryPlayer.tsx +++ b/frontend/src/components/LibraryPlayer.tsx @@ -36,7 +36,7 @@ export default function LibraryPlayer({ const qc = useQueryClient(); const fileUrl = useMemo(() => api.fileUrl(job.id), [job.id]); // Video vs audio-only comes from the stored media_kind (the single source every sibling reads — - // WatchPage/downloadRow/DownloadCenter) — NOT a re-derived container list, which would misclassify + // WatchPage/downloadRow/the tray) — NOT a re-derived container list, which would misclassify // an uploaded audio file whose container is outside a hardcoded set (uploads accept any container). // Only fall back to the old dimension+container heuristic for a legacy asset with a null media_kind. const asset = job.asset; diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index a539ec44..3a7e856e 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -120,12 +120,6 @@ export default function NavSidebar({ // events (the former separate bell is now folded into the inbox page). const clientUnread = useSyncExternalStore(subscribe, getUnreadCount, getUnreadCount); const unread = (unreadQuery.data?.count ?? 0) + clientUnread; - // Download center badge = items still working (queued/running/paused). Reuses the same - // lightweight per-user index the feed cards poll, so no extra request shape. - const dlIndexQuery = useLiveQuery(qk.downloadIndex(), api.downloadIndex, { - intervalMs: 30000, - }); - const dlActive = Object.values(dlIndexQuery.data ?? {}).filter((s) => s !== "done").length; // Scroll-affordance for the module list: hidden scrollbar + top/bottom edge fade (shared hook, // also used by the side panels) so a high-zoom overflow scrolls without a scrollbar. @@ -138,7 +132,6 @@ export default function NavSidebar({ // badge NUMBERS are the only nav-only concern (they need the live polling queries above). const BADGE: Partial> = { notifications: unread, - downloads: dlActive, }; const META = (p: Page): Omit => ({ icon: moduleDef(p).icon, diff --git a/frontend/src/components/NavigationProvider.dom.test.tsx b/frontend/src/components/NavigationProvider.dom.test.tsx index df1dcaaa..5d020e2f 100644 --- a/frontend/src/components/NavigationProvider.dom.test.tsx +++ b/frontend/src/components/NavigationProvider.dom.test.tsx @@ -206,6 +206,18 @@ describe("NavigationProvider · Back", () => { expect(window.location.search).toBe("?keep=1"); }); + it("lands where a retired page's content went, and the entry names that page", () => { + const { result } = render(); + act(() => result.current.setPage("plex")); + act(() => { + window.history.replaceState({ sfPage: "downloads" }, "", "/?page=downloads&keep=1"); + window.dispatchEvent(new PopStateEvent("popstate", { state: { sfPage: "downloads" } })); + }); + expect(result.current.page).toBe("files"); + expect(window.history.state).toMatchObject({ sfPage: "files" }); + expect(window.location.search).toBe("?page=files&keep=1"); + }); + // lib/history's own listener takes a spent focus stamp off the entry it lands on; the rewrite must // not put it back, or the next landing there (an overlay closing) would refocus a stale item. it("does not put back the focus stamp the landing spent", () => { diff --git a/frontend/src/components/NavigationProvider.tsx b/frontend/src/components/NavigationProvider.tsx index e9463e92..a8d18c0f 100644 --- a/frontend/src/components/NavigationProvider.tsx +++ b/frontend/src/components/NavigationProvider.tsx @@ -2,12 +2,11 @@ import { useCallback, useEffect, useMemo, useState, type ReactNode } from "react import { useLatest } from "../lib/useLatest"; import { useTranslation } from "react-i18next"; import { useMe } from "../lib/useMe"; -import { isPage, urlWithParams, type Page } from "../lib/urlState"; +import { resolvePage, urlWithParams, type Page } from "../lib/urlState"; import { LOGS_KEYS } from "../lib/logsUrlState"; import { pageTitleKey } from "../lib/pageMeta"; import { useDocumentObjectTitle } from "../lib/documentTitle"; import { setAccountRaw } from "../lib/storage"; -import { setNavigator } from "../lib/nav"; import { backStaysInDocument, closeOverlaysThenNavigate, @@ -156,9 +155,6 @@ export function NavigationProvider({ children }: { children: ReactNode }) { } }, []); - // Expose setPage to decoupled callers (download toast "View", etc.) via the lib/nav singleton. - useEffect(() => setNavigator(setPage), [setPage]); - // Leaving the feed ends any live YouTube search, so coming back shows the normal feed. Adjusted // DURING the render that changes the page, against the page we were last on: as an effect it ran // after the new page had already committed, so for one frame a module rendered with a live search @@ -206,17 +202,18 @@ export function NavigationProvider({ children }: { children: ReactNode }) { } function onPop(e: PopStateEvent) { // An entry can name a page this build no longer has (one pushed before a module was removed, - // reached by Back after a reload onto the new build); that lands on the feed, not on a crash. - // The entry is rewritten to say so: left alone, the address bar would keep naming the dead page - // and a click on Feed would be a no-op (setPage's same-page guard). It spreads the LIVE entry, - // not `e.state`: lib/history's listener ran first and already took the spent `sfNav` off it. + // reached by Back after a reload onto the new build); that lands where the page's content went + // (`resolvePage`), else on the feed, not on a crash. The entry is rewritten to say so: left + // alone, the address bar would keep naming the dead page and a click on the page it landed on + // would be a no-op (setPage's same-page guard). It spreads the LIVE entry, not `e.state`: + // lib/history's listener ran first and already took the spent `sfNav` off it. const sf: unknown = e.state?.sfPage; - const p: Page = typeof sf === "string" && isPage(sf) ? sf : "feed"; + const p: Page = (typeof sf === "string" && resolvePage(sf)) || "feed"; if (typeof sf === "string" && sf !== p) window.history.replaceState( { ...window.history.state, sfPage: p }, "", - urlWithParams({ page: null }), + urlWithParams({ page: p === "feed" ? null : p }), ); setPageState(p); setAccountRaw(PAGE_KEY, p); diff --git a/frontend/src/components/ProfileEditor.dom.test.tsx b/frontend/src/components/ProfileEditor.dom.test.tsx new file mode 100644 index 00000000..10a3017f --- /dev/null +++ b/frontend/src/components/ProfileEditor.dom.test.tsx @@ -0,0 +1,139 @@ +// @vitest-environment jsdom +// +// The download formats (Settings → Downloads, and the download dialog's "Manage formats"): one aligned +// list, built-ins first, each row saying what it saves. A row's one menu: a built-in's only +// Duplicate; the user's own Edit, Duplicate and Delete (behind a confirm). A duplicate is a NEW format +// with the original's settings and a name of its own. i18n is key-passthrough. +import { afterEach, describe, expect, it, vi } from "vitest"; +import { act, cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { api, type DownloadProfile } from "../lib/api"; + +const mocks = vi.hoisted(() => ({ confirm: vi.fn(async (_o?: unknown) => true) })); + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), +})); +vi.mock("./confirmContext", () => ({ useConfirm: () => mocks.confirm })); + +import { DownloadFormats } from "./ProfileEditor"; + +const SPEC = { + mode: "av", + max_height: 480, + container: "mp4", + audio_format: "m4a", + vcodec: null, + embed_subs: true, + embed_chapters: false, + embed_thumbnail: false, + sponsorblock: true, +}; +const PROFILES = [ + { id: 9, name: "Phone 480", is_builtin: false, spec: SPEC }, + { id: 1, name: "best", is_builtin: true, spec: { mode: "a", audio_format: "mp3" } }, +] as unknown as DownloadProfile[]; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + mocks.confirm.mockClear(); +}); + +async function mount() { + vi.spyOn(api, "downloadProfiles").mockResolvedValue(PROFILES); + render( + + + , + ); + await screen.findByText("Phone 480"); +} + +const rows = () => [...document.querySelectorAll("[data-nav-index]")]; +const rowOf = (text: string) => screen.getByText(text).closest("[data-nav-index]")!; +const menuRows = () => + screen.getAllByRole("menuitem", { hidden: true }).map((b) => b.textContent ?? ""); +const pick = (label: string) => + fireEvent.click( + screen.getAllByRole("menuitem", { hidden: true }).find((b) => b.textContent === label)!, + ); +async function openMenu(row: HTMLElement) { + act(() => { + fireEvent.contextMenu(row); + }); + await waitFor(() => + expect(screen.getAllByRole("menuitem", { hidden: true }).length).toBeGreaterThan(0), + ); +} + +describe("DownloadFormats", () => { + it("lists the built-ins first, each saying what it saves", async () => { + await mount(); + expect(rows().map((r) => r.textContent)).toEqual([ + expect.stringContaining("downloads.profiles.modeA · MP3"), + expect.stringContaining( + "downloads.profiles.modeAv · ≤480p · downloads.profiles.short.subs · downloads.profiles.short.sponsorblock", + ), + ]); + }); + + it("a built-in's menu only duplicates; the user's own edits, duplicates and deletes", async () => { + await mount(); + await openMenu(rows()[0]!); + expect(menuRows()).toEqual(["downloads.profiles.duplicate"]); + fireEvent.keyDown(document.activeElement!, { key: "Escape" }); + await openMenu(rowOf("Phone 480")); + expect(menuRows()).toEqual([ + "downloads.profiles.edit", + "downloads.profiles.duplicate", + "downloads.profiles.delete", + ]); + }); + + it("deletes only through the confirm", async () => { + const del = vi.spyOn(api, "deleteDownloadProfile").mockResolvedValue({} as never); + await mount(); + mocks.confirm.mockResolvedValueOnce(false); + await openMenu(rowOf("Phone 480")); + pick("downloads.profiles.delete"); + await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); + expect(del).not.toHaveBeenCalled(); + await openMenu(rowOf("Phone 480")); + pick("downloads.profiles.delete"); + await waitFor(() => expect(del).toHaveBeenCalledWith(9)); + }); + + it("a duplicate is a new format with the original's settings and a name of its own", async () => { + const create = vi.spyOn(api, "createDownloadProfile").mockResolvedValue({} as never); + const update = vi.spyOn(api, "updateDownloadProfile").mockResolvedValue({} as never); + await mount(); + await openMenu(rowOf("Phone 480")); + pick("downloads.profiles.duplicate"); + const name = await screen.findByPlaceholderText("downloads.profiles.namePlaceholder"); + expect((name as HTMLInputElement).value).toBe(""); + fireEvent.change(name, { target: { value: "Phone copy" } }); + fireEvent.click(screen.getByText("downloads.profiles.create")); + await waitFor(() => + expect(create).toHaveBeenCalledWith({ + name: "Phone copy", + spec: expect.objectContaining({ max_height: 480, embed_subs: true, sponsorblock: true }), + }), + ); + expect(update).not.toHaveBeenCalled(); + }); + + it("Edit saves over the format itself", async () => { + const update = vi.spyOn(api, "updateDownloadProfile").mockResolvedValue({} as never); + await mount(); + await openMenu(rowOf("Phone 480")); + pick("downloads.profiles.edit"); + const name = await screen.findByDisplayValue("Phone 480"); + fireEvent.change(name, { target: { value: "Phone" } }); + fireEvent.click(screen.getByText("downloads.profiles.save")); + await waitFor(() => + expect(update).toHaveBeenCalledWith(9, expect.objectContaining({ name: "Phone" })), + ); + }); +}); diff --git a/frontend/src/components/ProfileEditor.tsx b/frontend/src/components/ProfileEditor.tsx index d84454a5..15c7bd7f 100644 --- a/frontend/src/components/ProfileEditor.tsx +++ b/frontend/src/components/ProfileEditor.tsx @@ -1,8 +1,9 @@ import { useState } from "react"; import { qk } from "../lib/queryKeys"; import { useTranslation } from "react-i18next"; +import type { TFunction } from "i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Lock, Pencil, Plus, Trash2 } from "lucide-react"; +import { CopyPlus, Lock, Pencil, Plus, Trash2 } from "lucide-react"; import Modal from "./ui/Modal"; import { useConfirm } from "./confirmContext"; import { api, type DownloadProfile, type DownloadSpec } from "../lib/api"; @@ -10,11 +11,15 @@ import { profileName } from "../lib/vocab"; import { Button } from "./ui/Button"; import SegmentedToggle from "./ui/SegmentedToggle"; import FilterSelect from "./ui/FilterSelect"; -import { IconButton } from "./ui/IconButton"; import { CheckRow } from "./ui/NativeControls"; import { Input } from "./ui/Input"; -import { SURFACE } from "./ui/surfaces"; -import { SectionLabel } from "./ui/Headings"; +import { type ActionMenuItem } from "./ui/ActionMenu"; +import { ContextMenu } from "./ui/ContextMenu"; +import DataTable, { type Column } from "./ui/DataTable"; +import { menuGroups } from "./ui/menuGroups"; +import { MoreButton } from "./ui/MoreButton"; +import { LoadingState, StateMessage } from "./ui/QueryState"; +import { useContextMenu } from "./ui/useContextMenu"; const HEIGHTS = [null, 2160, 1440, 1080, 720, 480, 360]; @@ -46,14 +51,43 @@ function Check({ ); } -export default function ProfileEditor({ onClose }: { onClose: () => void }) { +// The download formats: the built-in ones (read-only, a base to start from) and the user's own, in one +// aligned list whose rows each have ONE menu (its "…", a right-click, a long-press, Shift+F10) — +// Edit · Duplicate · Delete, a built-in's only Duplicate. The new/edit form opens under the list. +// Settings → Downloads shows it in place; the download dialog's "Manage formats" in a modal. + +const MODE_KEY = { + av: "downloads.profiles.modeAv", + v: "downloads.profiles.modeV", + a: "downloads.profiles.modeA", +} as const; + +/** What a format saves, in a line: the content, the quality (or the audio format), the extras. */ +function specSummary(t: TFunction, spec: Partial): string { + const mode = spec.mode ?? "av"; + const parts = [ + t(MODE_KEY[mode]), + mode === "a" + ? (spec.audio_format ?? "m4a").toUpperCase() + : spec.max_height + ? `≤${spec.max_height}p` + : t("downloads.profiles.best"), + ]; + if (mode !== "a" && spec.embed_subs) parts.push(t("downloads.profiles.short.subs")); + if (spec.embed_chapters) parts.push(t("downloads.profiles.short.chapters")); + if (spec.embed_thumbnail) parts.push(t("downloads.profiles.short.thumbnail")); + if (spec.sponsorblock) parts.push(t("downloads.profiles.short.sponsorblock")); + return parts.join(" · "); +} + +export function DownloadFormats() { const { t } = useTranslation(); const qc = useQueryClient(); const confirm = useConfirm(); const profilesQ = useQuery({ queryKey: qk.downloadProfiles(), queryFn: api.downloadProfiles }); - const profiles = profilesQ.data ?? []; - const builtins = profiles.filter((p) => p.is_builtin); - const mine = profiles.filter((p) => !p.is_builtin); + // Built-ins first, then the user's own — the order the dialog's menu lists them in. + const all = profilesQ.data ?? []; + const profiles = [...all.filter((p) => p.is_builtin), ...all.filter((p) => !p.is_builtin)]; const [editing, setEditing] = useState(null); const [name, setName] = useState(""); @@ -73,6 +107,13 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { setSpec({ ...BLANK, ...p.spec }); setFormOpen(true); }; + // A new format from an existing one: its settings, and a name of its own to give. + const startCopy = (p: DownloadProfile) => { + setEditing(null); + setName(""); + setSpec({ ...BLANK, ...p.spec }); + setFormOpen(true); + }; const save = useMutation({ mutationFn: () => @@ -106,43 +147,112 @@ export default function ProfileEditor({ onClose }: { onClose: () => void }) { const patch = (p: Partial) => setSpec((s) => ({ ...s, ...p })); + const menu = useContextMenu({ keyOf: (p) => p.id }); + const items = (p: DownloadProfile): ActionMenuItem[] => + menuGroups( + p.is_builtin + ? [] + : [ + { + id: "edit", + label: t("downloads.profiles.edit"), + icon: , + onSelect: () => startEdit(p), + }, + ], + [ + { + id: "copy", + label: t("downloads.profiles.duplicate"), + icon: , + onSelect: () => startCopy(p), + }, + ], + p.is_builtin + ? [] + : [ + { + id: "delete", + label: t("downloads.profiles.delete"), + icon: , + danger: true, + onSelect: () => void onDelete(p), + }, + ], + ); + const columns: Column[] = [ + { + key: "name", + header: t("downloads.profiles.name"), + cardPrimary: true, + render: (p) => ( + + {profileName(t, p)} + {p.is_builtin && ( + + )} + + ), + }, + { + key: "spec", + header: t("downloads.profiles.saves"), + render: (p) => { + const summary = specSummary(t, p.spec); + return ( + + {summary} + + ); + }, + }, + { + key: "menu", + header: "", + width: "3rem", + align: "right", + cardLabel: false, + cardRow: true, + render: (p) => ( + + ), + }, + ]; + return ( - - {/* Existing formats */} -
- {t("downloads.profiles.builtin")} - {builtins.map((p) => ( -
- - {profileName(t, p)} -
- ))} - {mine.length > 0 && ( - - {t("downloads.profiles.yours")} - - )} - {mine.map((p) => ( -
- {p.name} - startEdit(p)} label={t("downloads.actions.rename")}> - - - onDelete(p)} - label={t("downloads.profiles.delete")} - > - - -
- ))} -
+
+ {profilesQ.isError && !profilesQ.data ? ( + void profilesQ.refetch()}> + {t("common.loadError")} + + ) : !profilesQ.data ? ( + + ) : ( +
+ String(p.id)} + pager={false} + keyboardNav + fixedLayout + rowProps={(p) => menu.triggerProps(p)} + /> +
+ )} + {!formOpen ? (
-
+ {/* One option a line: a CheckRow is inline, and in the wide Settings column four of them + ran together on one line. */} +
{spec.mode !== "a" && ( void }) {
)} + + ); +} + +export default function ProfileEditor({ onClose }: { onClose: () => void }) { + const { t } = useTranslation(); + return ( + + ); } diff --git a/frontend/src/components/SettingsPanel.downloads.dom.test.tsx b/frontend/src/components/SettingsPanel.downloads.dom.test.tsx new file mode 100644 index 00000000..6b63579f --- /dev/null +++ b/frontend/src/components/SettingsPanel.downloads.dom.test.tsx @@ -0,0 +1,52 @@ +// @vitest-environment jsdom +// +// Settings has a Downloads tab: the download formats and the sign-ins to sites that need one, both +// preferences of the downloading. The sign-ins left the Account tab for it. i18n is key-passthrough. +import { afterEach, describe, expect, it, vi } from "vitest"; +import { cleanup, fireEvent, render, screen } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useState } from "react"; +import { api } from "../lib/api"; + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), +})); +vi.mock("../lib/storage", async (importOriginal) => ({ + ...(await importOriginal()), + // Opened on the Downloads tab: Appearance, the default, needs the whole prefs provider. + useTabPersistedState: () => useState("downloads"), +})); +vi.mock("../lib/useMe", () => ({ + useMe: () => ({ id: 1, role: "user", email: "a@b.c", google_enabled: false }), +})); +vi.mock("./prefsContext", () => ({ usePrefs: () => ({ saveState: "idle" }) })); +vi.mock("./wizardContext", () => ({ useWizard: () => ({ openWizard: vi.fn() }) })); +vi.mock("./confirmContext", () => ({ useConfirm: () => vi.fn(async () => true) })); + +import SettingsPanel from "./SettingsPanel"; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); +}); + +function mount() { + vi.spyOn(api, "downloadProfiles").mockResolvedValue([]); + vi.spyOn(api, "downloadCredentials").mockResolvedValue({ hosts: [] } as never); + render( + + + , + ); +} + +describe("Settings · Downloads", () => { + it("holds the formats and the site sign-ins; Account no longer does", async () => { + mount(); + expect(await screen.findByText("settings.downloads.formats")).toBeTruthy(); + expect(screen.getByText("settings.downloadAuth.title")).toBeTruthy(); + fireEvent.click(screen.getByTestId("settings-tab-account")); + expect(screen.queryByText("settings.downloadAuth.title")).toBeNull(); + }); +}); diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 8c397753..657a4cee 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -4,7 +4,18 @@ import { qk } from "../lib/queryKeys"; import { formatDate, formatDateTime, formatUnit } from "../lib/format"; import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Activity, Bell, Check, Loader2, Monitor, RefreshCw, Trash2, User } from "lucide-react"; +import { + Activity, + Bell, + Check, + Download, + Loader2, + Monitor, + RefreshCw, + Trash2, + User, +} from "lucide-react"; +import { DownloadFormats } from "./ProfileEditor"; import DiagnosticsSettings from "./DiagnosticsSettings"; import { SCHEMES, type Scheme, type ThemePrefs } from "../lib/theme"; import { api, HttpError, type Me } from "../lib/api"; @@ -50,10 +61,11 @@ export interface PrefsController { saveState: PrefsSaveState; } -type TabId = "appearance" | "notifications" | "account" | "diagnostics"; +type TabId = "appearance" | "notifications" | "downloads" | "account" | "diagnostics"; const TABS: { id: TabId; icon: typeof Monitor }[] = [ { id: "appearance", icon: Monitor }, { id: "notifications", icon: Bell }, + { id: "downloads", icon: Download }, { id: "account", icon: User }, { id: "diagnostics", icon: Activity }, ]; @@ -101,6 +113,7 @@ export default function SettingsPanel() {
{tab === "appearance" && } {tab === "notifications" && } + {tab === "downloads" && } {tab === "account" && } {tab === "diagnostics" && }
@@ -615,7 +628,7 @@ function PlexWatchSync() { // (e.g. a private Facebook group you belong to) so downloads from it use YOUR login. Write-only — // the stored blob is never shown back; each row shows only the host + whether it's set + when. // A cookie-authed download is kept private to your account (never cache-shared with another user). -// Exported for a focused DOM test (write-only behaviour); rendered inside Account below. +// Exported for a focused DOM test (write-only behaviour); rendered on the Downloads tab below. export function DownloadAuth() { const { t, i18n } = useTranslation(); const qc = useQueryClient(); @@ -796,6 +809,23 @@ export function AccountHeader({ me }: { me: Me }) { ); } +// How this account downloads: its formats (the download dialog's choices) and its sign-ins to sites +// that need one. Preferences of the downloading, not of the account's identity. +function Downloads() { + const { t } = useTranslation(); + return ( + <> +
+

+ {t("settings.downloads.formatsIntro")} +

+ +
+ + + ); +} + function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) { const { t } = useTranslation(); const confirm = useConfirm(); @@ -825,8 +855,6 @@ function Account({ me, onOpenWizard }: { me: Me; onOpenWizard: () => void }) { {me.role === "admin" && me.plex_enabled && } - - {me.google_enabled ? ( // YouTube access requires Google OAuth; hidden when this instance has no Google configured.
diff --git a/frontend/src/components/UploadDialog.dom.test.tsx b/frontend/src/components/UploadDialog.dom.test.tsx index f0bd42e0..b0836ea8 100644 --- a/frontend/src/components/UploadDialog.dom.test.tsx +++ b/frontend/src/components/UploadDialog.dom.test.tsx @@ -15,7 +15,7 @@ const h = vi.hoisted(() => ({ // user's bandwidth on it. Generous by default; the cap test below lowers it. filesUsage: vi.fn(async () => ({ upload_max_bytes: 1_000_000 })), notify: vi.fn(), - navigateTo: vi.fn(), + showInTray: vi.fn(), openFile: vi.fn(), })); @@ -35,7 +35,7 @@ vi.mock("../lib/notifications", async (importOriginal) => ({ ...(await importOriginal()), notify: h.notify, })); -vi.mock("../lib/nav", () => ({ navigateTo: h.navigateTo })); +vi.mock("../lib/downloadQueue", () => ({ showInTray: h.showInTray })); import UploadDialog from "./UploadDialog"; import { FilesActionsContext } from "./filesContext"; @@ -67,7 +67,7 @@ const fileInput = () => document.querySelector('input[type="file"]') as HTMLInpu beforeEach(() => { h.uploadFile.mockClear(); h.openFile.mockClear(); - h.navigateTo.mockClear(); + h.showInTray.mockClear(); h.notify.mockClear(); h.filesUsage.mockClear(); h.filesUsage.mockResolvedValue({ upload_max_bytes: 1_000_000 }); @@ -115,8 +115,7 @@ describe("UploadDialog", () => { }); it("puts the file in the folder it was opened from, and its notice opens where it now is", async () => { - // Done: Files opens on the file itself, in its folder. Still converting: it is in the download - // queue until it is done. + // Done: Files opens on the file itself, in its folder. Still converting: the tray opens on it. h.uploadFile.mockResolvedValueOnce({ id: 42, status: "done", folder_id: 5 } as never); wrap(vi.fn(), 5); fireEvent.change(fileInput(), { target: { files: [new File(["x"], "a.mp4")] } }); @@ -125,7 +124,7 @@ describe("UploadDialog", () => { expect((h.uploadFile.mock.calls[0]![1] as { folderId?: number }).folderId).toBe(5); (h.notify.mock.calls[0]![0] as { action: { onClick: () => void } }).action.onClick(); expect(h.openFile).toHaveBeenLastCalledWith(42, 5); - expect(h.navigateTo).not.toHaveBeenCalled(); + expect(h.showInTray).not.toHaveBeenCalled(); cleanup(); h.notify.mockClear(); @@ -135,7 +134,7 @@ describe("UploadDialog", () => { fireEvent.click(screen.getByText("downloads.upload.submit")); await waitFor(() => expect(h.notify).toHaveBeenCalledTimes(1)); (h.notify.mock.calls[0]![0] as { action: { onClick: () => void } }).action.onClick(); - expect(h.navigateTo).toHaveBeenLastCalledWith("downloads"); + expect(h.showInTray).toHaveBeenLastCalledWith(43); }); it("opens ready to send a file dropped on the page", async () => { diff --git a/frontend/src/components/UploadDialog.tsx b/frontend/src/components/UploadDialog.tsx index 8c1f5ae5..348b98cb 100644 --- a/frontend/src/components/UploadDialog.tsx +++ b/frontend/src/components/UploadDialog.tsx @@ -10,7 +10,7 @@ import { isAborted } from "../lib/api/core"; import { notifyActionError } from "../lib/actionErrors"; import { invalidateDownloads } from "../lib/downloads"; import { notify } from "../lib/notifications"; -import { navigateTo } from "../lib/nav"; +import { showInTray } from "../lib/downloadQueue"; import type { DownloadJob } from "../lib/api"; import { useFilesActions } from "./filesContext"; import { formatBytes } from "../lib/format"; @@ -68,11 +68,10 @@ export default function UploadDialog({ // same reason, and `formAlerts.dom.test.tsx` is the test that exists because of it. const [pickSeq, setPickSeq] = useState(0); const { openFile } = useFilesActions(); - // A done file is in Files, opened on it; one the worker still converts is in the download queue - // until it is. + // A done file is in Files, opened on it; one the worker still converts is in the tray until it is. const openUploaded = (job: DownloadJob) => { if (job.status === "done") openFile(job.id, job.folder_id ?? null); - else navigateTo("downloads"); + else showInTray(job.id); }; const pick_ = (f: File) => { setFile(f); diff --git a/frontend/src/components/Welcome.tsx b/frontend/src/components/Welcome.tsx index ba604bac..f161967c 100644 --- a/frontend/src/components/Welcome.tsx +++ b/frontend/src/components/Welcome.tsx @@ -7,7 +7,7 @@ import { Clapperboard, Clock, Code2, - Download, + FolderOpen, Expand, Filter, Inbox, @@ -182,7 +182,7 @@ const GALLERY: { key: string; src: string }[] = [ { key: "playlists", src: "/welcome/playlists.webp" }, { key: "search", src: "/welcome/search.webp" }, { key: "player", src: "/welcome/player.webp" }, - { key: "downloads", src: "/welcome/downloads.webp" }, + { key: "files", src: "/welcome/files.webp" }, { key: "plex", src: "/welcome/plex.webp" }, ]; @@ -192,7 +192,7 @@ const FEATURES: { key: string; icon: LucideIcon }[] = [ { key: "search", icon: Search }, { key: "playlists", icon: ListVideo }, { key: "player", icon: PlayCircle }, - { key: "downloads", icon: Download }, + { key: "files", icon: FolderOpen }, { key: "plex", icon: Clapperboard }, { key: "private", icon: Lock }, ]; diff --git a/frontend/src/components/channelColumns.tsx b/frontend/src/components/channelColumns.tsx index d973a472..0aa9b8f5 100644 --- a/frontend/src/components/channelColumns.tsx +++ b/frontend/src/components/channelColumns.tsx @@ -252,6 +252,7 @@ export function buildChannelColumns(deps: ChannelColumnDeps): Column ({ + storage: undefined as unknown, + jobs: undefined as unknown, + confirm: vi.fn(async (_o?: unknown) => true), +})); + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ + i18n: { language: "en" }, + t: (key: string, o?: { size?: string; email?: string }) => + `${key}${o?.size ? `:${o.size}` : ""}${o?.email ? `:${o.email}` : ""}`, + }), +})); +vi.mock("./confirmContext", () => ({ useConfirm: () => mocks.confirm })); +vi.mock("../lib/useLiveQuery", () => ({ + useLiveQuery: (key: readonly unknown[]) => ({ + data: key[0] === qk.adminStorage()[0] ? mocks.storage : mocks.jobs, + isError: false, + isLoading: false, + refetch: vi.fn(), + }), +})); + +import { StorageTab } from "./downloadAdmin"; + +const GiB = 1024 ** 3; + +const storage = (): AdminStorage => ({ + files_on_disk: 3, + total_bytes: 3 * GiB, + total_assets: 3, + total_cap_bytes: 0, + per_user: [ + { + user_id: 4, + email: "anna@x", + footprint_bytes: GiB, + files: 27, + max_bytes: 5 * GiB, + unlimited: false, + }, + { + user_id: 1, + email: "peter@x", + footprint_bytes: 2 * GiB, + files: 180, + // Unlimited wins over the limit the row still carries. + max_bytes: 10 * GiB, + unlimited: true, + }, + ], +}); + +const job = (id: number, extra: Partial = {}) => + ({ + id, + status: "done", + display_name: `Clip ${id}`, + source_url: `https://example.com/${id}`, + user_id: 4, + user_email: "anna@x", + asset: { size_bytes: 1024 }, + ...extra, + }) as unknown as DownloadJob; + +afterEach(() => { + cleanup(); + vi.restoreAllMocks(); + mocks.confirm.mockClear(); +}); + +function mount() { + mocks.storage = storage(); + mocks.jobs = [job(7), job(8, { source_url: null, user_id: 1, user_email: "peter@x" })]; + render( + + + , + ); +} + +const rowOf = (text: string) => + screen.getAllByText(text)[0]!.closest("[data-nav-index]")!; +const menuRows = () => + screen.getAllByRole("menuitem", { hidden: true }).map((b) => b.textContent ?? ""); +const pick = (label: string) => + fireEvent.click( + screen.getAllByRole("menuitem", { hidden: true }).find((b) => b.textContent === label)!, + ); +async function openMenu(row: HTMLElement) { + act(() => { + fireEvent.contextMenu(row); + }); + await waitFor(() => + expect(screen.getAllByRole("menuitem", { hidden: true }).length).toBeGreaterThan(0), + ); +} + +describe("StorageTab", () => { + it("names each user's use against their own limit, and their files", () => { + mount(); + const anna = rowOf("anna@x"); + expect(anna.textContent).toMatch(/downloads\.admin\.ofQuota:5(\.0)? GB/); + expect(anna.textContent).toContain("27"); + expect(anna.querySelector('[role="meter"]')?.getAttribute("aria-valuenow")).toBe("20"); + expect(rowOf("peter@x").textContent).toContain("downloads.admin.unlimitedShort"); + }); + + it("a user's menu edits their quota, and resets it only through the confirm", async () => { + vi.spyOn(api, "adminGetDownloadQuota").mockReturnValue(new Promise(() => {}) as never); + const reset = vi.spyOn(api, "adminResetDownloadQuota").mockResolvedValue({} as never); + mount(); + await openMenu(rowOf("anna@x")); + expect(menuRows()).toEqual(["downloads.admin.editQuota", "downloads.admin.reset"]); + mocks.confirm.mockResolvedValueOnce(false); + pick("downloads.admin.reset"); + await waitFor(() => expect(mocks.confirm).toHaveBeenCalledTimes(1)); + expect(reset).not.toHaveBeenCalled(); + await openMenu(rowOf("anna@x")); + pick("downloads.admin.reset"); + await waitFor(() => expect(reset).toHaveBeenCalledWith(4)); + await openMenu(rowOf("anna@x")); + pick("downloads.admin.editQuota"); + expect(await screen.findByText("downloads.admin.quotaTitle:anna@x")).toBeTruthy(); + expect(api.adminGetDownloadQuota).toHaveBeenCalledWith(4); + }); + + it("a download's menu opens its source, copies, and opens its OWNER's quota", async () => { + vi.spyOn(api, "adminGetDownloadQuota").mockReturnValue(new Promise(() => {}) as never); + const open = vi.spyOn(window, "open").mockReturnValue(null); + mount(); + await openMenu(rowOf("Clip 7")); + expect(menuRows()).toEqual([ + "downloads.admin.openSource", + "common.copy.title", + "common.copy.link", + "downloads.admin.editUserQuota", + ]); + pick("downloads.admin.openSource"); + expect(open).toHaveBeenCalledWith("https://example.com/7", "_blank", "noopener,noreferrer"); + // One with no source link offers neither the link nor its copy. + await openMenu(rowOf("Clip 8")); + expect(menuRows()).toEqual(["common.copy.title", "downloads.admin.editUserQuota"]); + pick("downloads.admin.editUserQuota"); + await waitFor(() => expect(api.adminGetDownloadQuota).toHaveBeenCalledWith(1)); + }); +}); diff --git a/frontend/src/components/downloadAdmin.tsx b/frontend/src/components/downloadAdmin.tsx index dcc59e4e..55e13286 100644 --- a/frontend/src/components/downloadAdmin.tsx +++ b/frontend/src/components/downloadAdmin.tsx @@ -2,23 +2,33 @@ import { useState } from "react"; import { qk } from "../lib/queryKeys"; import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { Settings2 } from "lucide-react"; +import clsx from "clsx"; +import { ExternalLink, RotateCcw, Settings2 } from "lucide-react"; import Modal, { ModalFooter } from "./ui/Modal"; import { useLiveQuery } from "../lib/useLiveQuery"; -import { api } from "../lib/api"; +import { api, type AdminStorageUser, type DownloadJob } from "../lib/api"; import { formatBytes } from "../lib/format"; import { Button } from "./ui/Button"; import { StatusPill } from "./downloadRow"; import { jobTitle } from "./downloadTitle"; -import { IconButton } from "./ui/IconButton"; +import { useConfirm } from "./confirmContext"; +import { useCopyItems } from "./copyItems"; +import { type ActionMenuItem } from "./ui/ActionMenu"; +import { ContextMenu } from "./ui/ContextMenu"; +import DataTable, { type Column } from "./ui/DataTable"; +import { Section } from "./ui/form"; +import { menuGroups } from "./ui/menuGroups"; +import { MoreButton } from "./ui/MoreButton"; +import { useContextMenu } from "./ui/useContextMenu"; import { Input } from "./ui/Input"; import { CheckRow } from "./ui/NativeControls"; import { LoadingState, StateMessage } from "./ui/QueryState"; import { SURFACE } from "./ui/surfaces"; import UserPicker from "./UserPicker"; -// The admin "System" tab — storage overview, per-user footprint, quota editor, and the global job -// list. Split out of DownloadCenter (self-contained, admin-only surface). +// The admin Users page's "Storage" tab: the disk in three numbers, every user's footprint against +// their own limit (the quota editor), and every user's downloads. Both lists are tables with aligned +// columns and one menu per row (its "…", a right-click, a long-press, Shift+F10). const GiB = 1024 ** 3; @@ -173,18 +183,201 @@ function QuotaUserPicker({ onPick }: { onPick: (u: { id: number; email: string } ); } - -export function AdminSystem() { +export function StorageTab() { const { t, i18n } = useTranslation(); const lang = i18n.language; + const qc = useQueryClient(); + const confirm = useConfirm(); + const copy = useCopyItems(); const storage = useLiveQuery(qk.adminStorage(), api.adminDownloadStorage, { intervalMs: 5000 }); const jobs = useLiveQuery(qk.adminDownloads(), api.adminDownloads, { intervalMs: 5000 }); const [quotaFor, setQuotaFor] = useState<{ id: number; email: string } | null>(null); const s = storage.data; + + const reset = useMutation({ + mutationFn: (userId: number) => api.adminResetDownloadQuota(userId), + onSuccess: (_d, userId) => { + void qc.invalidateQueries({ queryKey: qk.dlQuota(userId) }); + void qc.invalidateQueries({ queryKey: qk.adminStorage() }); + }, + meta: { failTitle: "errors.writeFailed.downloadQuotaReset" }, + }); + const onReset = async (u: AdminStorageUser) => { + const ok = await confirm({ + title: t("downloads.admin.resetTitle"), + message: t("downloads.admin.resetBody", { email: u.email ?? "" }), + confirmLabel: t("downloads.admin.reset"), + }); + if (ok) reset.mutate(u.user_id); + }; + + // --- every user ------------------------------------------------------------------------------ + const userMenu = useContextMenu({ keyOf: (u) => u.user_id }); + const userItems = (u: AdminStorageUser): ActionMenuItem[] => [ + { + id: "quota", + label: t("downloads.admin.editQuota"), + icon: , + onSelect: () => setQuotaFor({ id: u.user_id, email: u.email ?? "" }), + }, + { + id: "reset", + label: t("downloads.admin.reset"), + icon: , + onSelect: () => void onReset(u), + }, + ]; + // The bar sits beside the name and the number after it: a right-aligned "Used" before a + // left-aligned bar ran the two headers together into one phrase. + const userColumns: Column[] = [ + { + key: "user", + header: t("downloads.admin.columns.user"), + cardPrimary: true, + sortable: true, + sortValue: (u) => u.email ?? "", + render: (u) => {u.email}, + }, + { + key: "quota", + header: t("downloads.admin.columns.quota"), + width: "9rem", + sortable: true, + sortValue: (u) => (u.unlimited || !u.max_bytes ? -1 : u.footprint_bytes / u.max_bytes), + render: (u) => , + }, + { + key: "used", + header: t("downloads.admin.columns.used"), + width: "6.5rem", + align: "right", + sortable: true, + sortValue: (u) => u.footprint_bytes, + render: (u) => {formatBytes(u.footprint_bytes, lang)}, + }, + { + key: "files", + header: t("downloads.admin.columns.files"), + width: "4.5rem", + align: "right", + sortable: true, + sortValue: (u) => u.files, + render: (u) => {u.files}, + }, + { + key: "menu", + header: "", + width: "3rem", + align: "right", + cardLabel: false, + cardRow: true, + render: (u) => ( + + ), + }, + ]; + + // --- every download ---------------------------------------------------------------------------- + const jobMenu = useContextMenu({ keyOf: (j) => j.id }); + const jobItems = (j: DownloadJob): ActionMenuItem[] => { + const source = j.source_url; + const owner = j.user_id; + return menuGroups( + source + ? [ + { + id: "open", + label: t("downloads.admin.openSource"), + icon: , + // A new tab only opens inside the click (a popup blocker refuses one a task later). + inGesture: true, + onSelect: () => void window.open(source, "_blank", "noopener,noreferrer"), + }, + ] + : [], + copy(jobTitle(j), source), + owner != null + ? [ + { + id: "quota", + label: t("downloads.admin.editUserQuota"), + icon: , + onSelect: () => setQuotaFor({ id: owner, email: j.user_email ?? "" }), + }, + ] + : [], + ); + }; + const jobColumns: Column[] = [ + { + key: "title", + header: t("downloads.admin.columns.title"), + cardPrimary: true, + sortable: true, + sortValue: (j) => jobTitle(j), + render: (j) => ( + + {jobTitle(j)} + + ), + }, + { + key: "user", + header: t("downloads.admin.columns.user"), + width: "11rem", + sortable: true, + sortValue: (j) => j.user_email ?? "", + narrowHidden: true, + render: (j) => {j.user_email}, + }, + { + key: "status", + header: t("downloads.admin.columns.status"), + width: "7rem", + sortable: true, + sortValue: (j) => j.status, + render: (j) => , + }, + { + key: "size", + header: t("downloads.admin.columns.size"), + width: "6rem", + align: "right", + sortable: true, + sortValue: (j) => j.asset?.size_bytes ?? 0, + render: (j) => ( + + {j.asset?.size_bytes ? formatBytes(j.asset.size_bytes, lang) : "—"} + + ), + }, + { + key: "menu", + header: "", + width: "3rem", + align: "right", + cardLabel: false, + cardRow: true, + render: (j) => ( + + ), + }, + ]; + return (
{s && ( -
+
{[ [t("downloads.admin.filesOnDisk"), String(s.files_on_disk)], [t("downloads.admin.totalSize"), formatBytes(s.total_bytes, lang)], @@ -195,65 +388,103 @@ export function AdminSystem() { ].map(([label, val]) => (
{label}
-
{val}
+
{val}
))}
)} - {s && ( -
-
-
{t("downloads.admin.perUser")}
- {/* Set a quota for any user, regardless of whether they've downloaded anything. */} - -
-
- {s.per_user.map((u) => ( -
- {u.email} - {formatBytes(u.footprint_bytes, lang)} - setQuotaFor({ id: u.user_id, email: u.email ?? "" })} - label={t("downloads.admin.editQuota")} - > - - -
- ))} - {s.per_user.length === 0 && ( -
{t("downloads.admin.noFootprint")}
- )} -
+
+ {/* Set a quota for any user, regardless of whether they've downloaded anything. */} +
+
- )} - -
{t("downloads.tabs.system")}
-
- {(jobs.data ?? []).map((j) => ( -
- {jobTitle(j)} - {j.user_email} - - - {j.asset?.size_bytes ? formatBytes(j.asset.size_bytes, lang) : ""} - + {storage.isError && !s ? ( + void storage.refetch()}> + {t("common.loadError")} + + ) : !s ? ( + + ) : ( +
+ String(u.user_id)} + pageSize={25} + keyboardNav + fixedLayout + rowProps={(u) => userMenu.triggerProps(u)} + emptyText={t("downloads.admin.noFootprint")} + />
- ))} - {jobs.data && jobs.data.length === 0 && ( -
{t("downloads.empty.admin")}
)} -
+
+
+ {jobs.isError && !jobs.data ? ( + void jobs.refetch()}> + {t("common.loadError")} + + ) : !jobs.data ? ( + + ) : ( +
+ String(j.id)} + pageSize={25} + keyboardNav + fixedLayout + rowProps={(j) => jobMenu.triggerProps(j)} + emptyText={t("downloads.empty.admin")} + /> +
+ )} +
+ + + {quotaFor && ( setQuotaFor(null)} /> )}
); } + +/** How full a user is against their own limit: a bar, and the limit under it. */ +function QuotaFill({ user, lang }: { user: AdminStorageUser; lang: string }) { + const { t } = useTranslation(); + if (user.unlimited || !user.max_bytes) + return {t("downloads.admin.unlimitedShort")}; + const pct = Math.min(100, Math.round((user.footprint_bytes / user.max_bytes) * 100)); + return ( + + + = 90 ? "bg-danger" : "bg-accent")} + style={{ width: `${pct}%` }} + /> + + + {t("downloads.admin.ofQuota", { size: formatBytes(user.max_bytes, lang) })} + + + ); +} diff --git a/frontend/src/components/downloadModals.tsx b/frontend/src/components/downloadModals.tsx index f4078b49..ae264ce9 100644 --- a/frontend/src/components/downloadModals.tsx +++ b/frontend/src/components/downloadModals.tsx @@ -10,8 +10,8 @@ import { IconBtn } from "./downloadRow"; import { failCopyKeys, failTitleKey } from "../lib/downloadFailure"; import { Input } from "./ui/Input"; -// Per-item user modals split out of DownloadCenter (self-contained, prop-driven): rename/metadata -// editing and the failure-detail window. +// Per-item user modals (self-contained, prop-driven): a library file's details (Files) and a failed +// download's reasons (the tray). // Edit a library file's details: its channel (name + link) and extra reference links. Display-only // overrides — the on-disk file is never touched. Its name is renamed in place in the Files list. diff --git a/frontend/src/components/downloadRow.dom.test.tsx b/frontend/src/components/downloadRow.dom.test.tsx index deba7974..f92cc770 100644 --- a/frontend/src/components/downloadRow.dom.test.tsx +++ b/frontend/src/components/downloadRow.dom.test.tsx @@ -1,11 +1,9 @@ // @vitest-environment jsdom // -// Library-row metadata: the completed row surfaces the concrete container/format alongside the -// already-shown size. The format chip reads the raw `asset.container` (uppercased in the UI), so it must -// render when present and be absent when the asset has no container yet (queued/running before the worker -// fills it). i18n is key-passthrough here. +// A job's picture (the Files list and grid): with none at all, a quiet glyph of what it is; a remote +// thumbnail that fails falls back to the same glyph. i18n is key-passthrough here. import { describe, it, expect, afterEach, vi } from "vitest"; -import { render, screen, cleanup, fireEvent } from "@testing-library/react"; +import { render, cleanup, fireEvent } from "@testing-library/react"; vi.mock("react-i18next", async (importOriginal) => { const actual = await importOriginal(); @@ -15,7 +13,7 @@ vi.mock("react-i18next", async (importOriginal) => { }; }); -import { DownloadRow, IconBtn } from "./downloadRow"; +import { JobThumb } from "./downloadRow"; import type { DownloadJob } from "../lib/api"; const baseJob: DownloadJob = { @@ -82,201 +80,15 @@ const withAsset = ( afterEach(cleanup); -describe("DownloadRow format chip", () => { - it("shows the uppercased container next to the size when ready", () => { - render(); - // Raw container is stored lowercase; the row uppercases it via CSS, so the text node stays "mp4". - expect(screen.getByText(/·\s*mp4/i)).toBeTruthy(); - expect(screen.getByText(/142 MB/)).toBeTruthy(); - }); - - it("omits the format chip when the asset has no container yet", () => { - const { container } = render(); - expect(container.textContent).not.toMatch(/·\s*(mp4|m4a|webm)/i); - }); -}); - -// A generic uploaded file (media_kind='file') has no video frame and no timeline: the row -// shows a neutral file glyph instead of a thumbnail , and no duration chip (it stays media info -// like the container/size). Media rows keep their thumbnail path (an , or empty until a poster). -describe("DownloadRow file kind", () => { - it("renders a file glyph, not a thumbnail , for a generic file", () => { - const { container } = render( - , - ); - // The file glyph is an inline svg; no element is emitted for a file. - expect(container.querySelector("img")).toBeNull(); - expect(container.querySelector("svg")).not.toBeNull(); - // File info still reads (container + size), but there is no duration chip. - expect(screen.getByText(/·\s*pdf/i)).toBeTruthy(); - expect(screen.getByText(/2\.3 MB/)).toBeTruthy(); - }); - - it("keeps the thumbnail path (an when a poster exists) for a media asset", () => { - const { container } = render( - , - ); - expect(container.querySelector("img")).not.toBeNull(); - }); -}); - -describe("DownloadRow · keyboard", () => { - it("is a list item only when it is given a position", () => { - const { container, rerender } = render(); - expect(container.firstElementChild?.getAttribute("data-nav-index")).toBe("3"); - rerender(); - expect(container.firstElementChild?.hasAttribute("data-nav-index")).toBe(false); - }); - - it("marks the action that opens the row, and only that one", () => { - render( - - {}} title="play"> - p - - {}} title="delete"> - d - - , - ); - expect(screen.getByTitle("play").hasAttribute("data-nav-open")).toBe(true); - expect(screen.getByTitle("delete").hasAttribute("data-nav-open")).toBe(false); - }); - - // The row once resized its actions with a `[&_button]:size-8` override, which a save LINK in the - // same row escaped (28px beside 32px buttons). The size is the primitive's own rung, set by IconBtn. - it("sizes its actions through the primitive's rung, not a descendant override", () => { - const { container } = render( - - {}} title="play"> - p - - , - ); - expect(screen.getByTitle("play").className).toContain("size-8"); - expect(container.innerHTML).not.toContain("[&_button]"); - }); -}); - -// The row answers to its own width (a container query) and its meta line never wraps — on -// the iPad, zoomed in, its "18.9 / MB" pieces broke into a jumble (the owner, 2026-09-27). jsdom lays -// nothing out, so this holds the structure the layout rests on; the widths were measured in a browser. -describe("DownloadRow · narrow", () => { - it("keeps the meta line one cut line of inline text, not a flex row that wraps its pieces", () => { - const { container } = render( - , - ); - const meta = container.querySelector("[data-dl-meta]")!; - expect(meta.className.split(/\s+/)).toContain("truncate"); - expect(meta.className).not.toMatch(/(^|\s)(inline-)?flex(\s|$)/); - for (const el of meta.querySelectorAll("*")) - expect(el.className.toString()).not.toMatch(/(^|\s)(inline-)?flex(\s|$)/); - }); - - it("is its own size container and stacks below 40rem", () => { - const { container } = render(); - const row = container.firstElementChild as HTMLElement; - expect(row.className).toContain("@container/dlrow"); - const grid = row.firstElementChild as HTMLElement; - expect(grid.className).toContain( - "@max-[40rem]/dlrow:[grid-template-areas:'thumb_head''meta_meta''foot_foot''prog_prog']", - ); - }); - - it("stacked, puts the source link and the actions on ONE wrapping line, not a line each", () => { - const { container } = render( - - {}} title="play"> - x - - , - ); - const link = container.querySelector("a[href='https://example.com/v']")!; - const foot = screen.getByTitle("play").closest(".contents")!; - expect(foot.contains(link)).toBe(true); - for (const c of ["@max-[40rem]/dlrow:[grid-area:foot]", "@max-[40rem]/dlrow:flex-wrap"]) - expect(foot.className).toContain(c); - }); - - it("gives the failure link a real 24px line on a mouse, not the text's 20 (DESIGN B1)", () => { - render( - {}} />, - ); - const link = screen.getByTitle("downloads.errors.fail.title"); - const cls = link.className.split(/\s+/); - expect(cls).toEqual(expect.arrayContaining(["inline-block", "min-h-6", "leading-6"])); - }); - - it("draws nothing for an empty link list (a bare 0 once leaked into the row)", () => { - const { container } = render(); - const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT); - const texts: string[] = []; - while (walker.nextNode()) texts.push(walker.currentNode.textContent ?? ""); - expect(texts).not.toContain("0"); - }); -}); - -// A link queued (or failed) before its details: the title, the channel line and the link line all -// fell back to the same URL (the owner, 2026-09-27). Now the title IS the short link, once. -describe("DownloadRow · a link with no details yet", () => { - const bare: DownloadJob = { - ...baseJob, - status: "error", - source_kind: "url", - source_ref: "https://www.facebook.com/watch?v=12345", - source_url: "https://www.facebook.com/watch?v=12345", - display_name: null, - asset: null, - }; - - it("shows the short link once, as the title's link, with no channel line", () => { - const { container } = render(); - const links = [...container.querySelectorAll("a")].filter((a) => a.href === bare.source_url); - expect(links.map((a) => a.textContent)).toEqual(["www.facebook.com/watch?v=12345"]); - expect(container.textContent).not.toContain("https://"); - }); - - // Measured at 21px on the Downloads page: every link in the row stands on a 24px line on - // a mouse (DESIGN B1), as the link line and the failure link already did. - it("gives the title link and the channel link a 24px line", () => { - const { container } = render( - , - ); - const title = [...container.querySelectorAll("a")].find((a) => a.href === bare.source_url)!; - const channel = container.querySelector("a[href='https://example.com/c']")!; - // inline-block: an inline link's box is the font's height (21px) whatever its line height. - for (const a of [title, channel]) - expect(a.className.split(/\s+/)).toEqual( - expect.arrayContaining(["inline-block", "leading-6"]), - ); - }); - - it("keeps the named row as it was: title text, and the link on its own line", () => { - const named = { ...bare, display_name: "A clip" }; - const { container } = render(); - expect(screen.getByText("A clip").closest("a")).toBeNull(); - const links = [...container.querySelectorAll("a")].filter((a) => a.href === bare.source_url); - expect(links.map((a) => a.textContent)).toEqual(["www.facebook.com/watch?v=12345"]); - }); -}); - // No picture at all: a quiet glyph of what it is instead of an empty box (the owner, 2026-09-27). -describe("DownloadRow · no picture", () => { +describe("JobThumb · no picture", () => { it("draws a film glyph for a video, a note for audio", () => { - const { container, unmount } = render(); + const { container, unmount } = render(); expect(container.querySelector("img")).toBeNull(); expect(container.querySelector("svg.lucide-film")).not.toBeNull(); unmount(); const { container: audio } = render( - , + , ); expect(audio.querySelector("svg.lucide-music")).not.toBeNull(); }); @@ -284,7 +96,7 @@ describe("DownloadRow · no picture", () => { // failed too (a dead CDN link), the row drew the browser's broken-image mark. it("falls back to the glyph when the remote picture fails too", () => { const { container } = render( - , + , ); const img = container.querySelector("img")!; expect(img.getAttribute("src")).toBe("https://cdn.example/dead.jpg"); @@ -293,27 +105,3 @@ describe("DownloadRow · no picture", () => { expect(container.querySelector("svg.lucide-film")).not.toBeNull(); }); }); - -// The phone-safe conversion reports ffmpeg's real progress, so it gets the % bar a download has; -// a yt-dlp post-step reports none and keeps the pulse. -describe("DownloadRow · a running conversion", () => { - const running = (phase: string): DownloadJob => ({ - ...baseJob, - status: "running", - progress: 40, - phase, - }); - - it("shows its phase and its %", () => { - const { container } = render(); - expect(screen.getByText("downloads.phase.converting")).toBeTruthy(); - expect(screen.getByText("40%")).toBeTruthy(); - expect(container.querySelector(".animate-pulse")).toBeNull(); - }); - - it("a post-step with no % keeps the pulse", () => { - const { container } = render(); - expect(screen.queryByText("40%")).toBeNull(); - expect(container.querySelector(".animate-pulse")).not.toBeNull(); - }); -}); diff --git a/frontend/src/components/downloadRow.tsx b/frontend/src/components/downloadRow.tsx index 7dfb086a..f734b8e6 100644 --- a/frontend/src/components/downloadRow.tsx +++ b/frontend/src/components/downloadRow.tsx @@ -1,27 +1,14 @@ import { useState, type ReactNode } from "react"; import { useTranslation } from "react-i18next"; -import { CalendarClock, File, Film, Link2, Music, Scissors } from "lucide-react"; +import { File, Film, Music } from "lucide-react"; import clsx from "clsx"; import { api, type DownloadJob } from "../lib/api"; -import { - formatBytes, - formatDateTime, - formatDuration, - formatEta, - formatSpeed, - relativeTime, -} from "../lib/format"; -import { navItemAttrs } from "../lib/listNav"; -import { failCopyKeys, failTitleKey } from "../lib/downloadFailure"; -import { displayUrl, hasOwnTitle, jobTitle } from "./downloadTitle"; import { IconButton } from "./ui/IconButton"; import { MediaProgress } from "./ui/MediaCard"; -import { Pressable } from "./ui/Pressable"; -import { SURFACE } from "./ui/surfaces"; -// Download-row presentation + the small shared primitives (status pill, icon button, the job's -// picture), split out of DownloadCenter (all purely prop-driven, no shared state). Rendered by the -// queue and the admin tab; the Files list borrows the picture. +// The small shared primitives of a download (status pill, icon button, the job's picture), all purely +// prop-driven: the admin Storage tab shows the status, the Files list and grid the picture, the +// details dialog the icon button. const STATUS_CLS: Record = { queued: "text-muted", @@ -32,10 +19,6 @@ const STATUS_CLS: Record = { canceled: "text-muted", }; -// Byte-progress phases (show a % bar) vs ffmpeg post-steps (no %, indeterminate pulse). -// "editing" (the phase-2 editor) and "converting" (the phone-safe conversion of a download or an -// upload) report a real % from ffmpeg's out_time, so they get a bar. -const DOWNLOAD_PHASES = ["video", "audio", "editing", "converting"]; const PHASE_KEYS = [ "video", "audio", @@ -143,16 +126,6 @@ export function JobThumb({ job, box = THUMB_BOX }: { job: DownloadJob; box?: str ); } -// The channel to show under the title: a user override wins over the auto-extracted asset value. -// `url` is null for sources without a channel link (e.g. reddit) — then it renders as plain text. No -// channel known yet → no line: the ref it once fell back to repeated the title (the owner, 2026-09-27). -function jobChannel(job: DownloadJob): { name: string | null; url: string | null } { - return { - name: job.display_uploader || job.asset?.uploader || null, - url: job.display_uploader_url || job.asset?.uploader_url || null, - }; -} - export function IconBtn({ onClick, title, @@ -179,202 +152,3 @@ export function IconBtn({ ); } - -// A link inside the row keeps the BROWSER's own right-click menu (open in a new tab, copy the -// address): the row's menu answers everywhere else on it. -const nativeMenu = { onContextMenu: (e: React.MouseEvent) => e.stopPropagation() }; - -// "Downloaded from" reference: opens the original page in a new tab. Its copy moved into the row's -// menu ("Copy link"); an extra link copies through the browser's own link menu. -function SourceRef({ url }: { url: string }) { - const { t } = useTranslation(); - return ( - - ); -} - -// Channel under the title — a clickable link when a channel URL is known, plain text otherwise. -function ChannelLine({ job }: { job: DownloadJob }) { - const { name, url } = jobChannel(job); - if (!name) return null; - if (url) { - return ( - - ); - } - return
{name}
; -} - -export function DownloadRow({ - job, - onShowError, - navIndex, - rowProps, - more, - children, -}: { - job: DownloadJob; - onShowError?: (job: DownloadJob) => void; - /** Its position in a keyboard-walkable list (lib/listNav). */ - navIndex?: number; - /** The list's menu triggers (right-click, long-press, Shift+F10): `useContextMenu().triggerProps`. */ - rowProps?: Record; - /** The row's "…", last in its action strip. */ - more?: ReactNode; - children?: ReactNode; -}) { - const { t, i18n } = useTranslation(); - const lang = i18n.language; - const running = job.status === "running"; - const linkTitle = !hasOwnTitle(job) && !!job.source_url; - const sourceLink = linkTitle ? null : job.source_url; - return ( -
-
-
- -
-
-
- {(job.job_kind === "edit" || job.job_kind === "clip") && ( - - {t("downloads.clipBadge")} - - )} - {linkTitle ? ( - // No name yet: the title IS the link, so the link line below leaves it out (it said the - // same URL a third time). - - {jobTitle(job)} - - ) : ( - jobTitle(job) - )} -
- -
- {/* The meta line never wraps (the owner, 2026-09-27: on the iPad its "18.9 / MB" pieces broke - into a jumble). One line, cut at its end, so the least telling part (the age) goes first. */} -
- - {job.asset?.container ? ( - // Concrete container (mp4 / m4a / webm …) — reads as the file format, and its audio-vs-video - // extension is the video/audio distinction the running-only phase pill can't show at rest. - · {job.asset.container} - ) : null} - {job.asset?.size_bytes ? ( - · {formatBytes(job.asset.size_bytes, lang)} - ) : null} - {job.asset?.duration_s ? ( - · {formatDuration(job.asset.duration_s)} - ) : null} - {job.status === "done" && job.created_at ? ( - - {" · "} - {" "} - {relativeTime(job.created_at, lang, t)} - - ) : null} - {job.status === "error" ? ( - <> - {" "} - onShowError?.(job)} - className="text-danger inline-block leading-6 min-h-6 touch-target pointer-coarse:leading-(--touch-target)" - title={t(failTitleKey(job))} - > - · {t(failCopyKeys(job, "short"))}{" "} - - {t("downloads.errors.fail.detailsLink")} - - - - ) : null} -
- {/* Wide, this wrapper is `contents`: the source and the actions are grid items of their own - (src under the meta, act down the right). Stacked, it is ONE wrapping line: the link and - the actions side by side while they fit, the actions under the link only when they don't - (at 375). On touch each is a 44px box, so separate lines stacked 44px gaps (the owner, - 2026-09-27: "what are those huge empty parts?"). */} -
- {(!!sourceLink || !!job.extra_links?.length) && ( -
- {sourceLink && } - {job.extra_links?.map((url) => ( - - ))} -
- )} - {/* The row's visible actions — its primary one and the save link — then its "…": the md - rung here (the row stands alone), the sm rung in the modals, one IconBtn for both. */} -
- {children} - {more} -
-
- {running && ( -
- {job.phase && !DOWNLOAD_PHASES.includes(job.phase) ? ( - // ffmpeg post-steps aren't byte-progress — show an indeterminate pulse, no %. -
-
-
- ) : ( - <> -
-
-
-
- {job.progress}% - {job.speed_bps ? {formatSpeed(job.speed_bps, lang)} : null} - {job.eta_s ? {formatEta(t, job.eta_s)} : null} -
- - )} -
- )} -
-
- ); -} diff --git a/frontend/src/components/downloadTitle.ts b/frontend/src/components/downloadTitle.ts index 105e321c..df212595 100644 --- a/frontend/src/components/downloadTitle.ts +++ b/frontend/src/components/downloadTitle.ts @@ -1,7 +1,7 @@ import type { DownloadJob } from "../lib/api"; // A download job's display title (a user rename wins over the extracted asset title), shared by the -// row, the admin list and the library player. Its own module so downloadRow exports only components. +// tray, the Files list, the admin Storage tab and the library player. // With no name yet — a link queued before its details arrive, or one that failed first — it is the // link itself, shortened the way the row shows a link, never the raw ref (a URL, or a bare video id). export function jobTitle(job: DownloadJob): string { @@ -12,13 +12,8 @@ export function jobTitle(job: DownloadJob): string { ); } -/** The job has a name of its own (a rename or an extracted title), not the link standing in for one. */ -export function hasOwnTitle(job: DownloadJob): boolean { - return !!(job.display_name || job.asset?.title); -} - // Compact display of a URL: drop the protocol + trailing slash so "youtube.com/watch?v=…" fits. -export function displayUrl(url: string): string { +function displayUrl(url: string): string { try { const u = new URL(url); return (u.host + u.pathname + u.search).replace(/\/$/, ""); diff --git a/frontend/src/components/listNav.wiring.contract.test.ts b/frontend/src/components/listNav.wiring.contract.test.ts index c34899a0..9d20ef98 100644 --- a/frontend/src/components/listNav.wiring.contract.test.ts +++ b/frontend/src/components/listNav.wiring.contract.test.ts @@ -20,7 +20,6 @@ const CARDS = [ "EpisodeCard", "PosterTile", "ShelfTile", - "DownloadRow", ]; const LISTS = ["NavGrid", "Carousel", "VirtualGrid"]; diff --git a/frontend/src/components/moduleRegistry.tsx b/frontend/src/components/moduleRegistry.tsx index f0129bc4..27945fcf 100644 --- a/frontend/src/components/moduleRegistry.tsx +++ b/frontend/src/components/moduleRegistry.tsx @@ -28,7 +28,6 @@ const PlexSidebar = lazyWithPreload(() => import("./PlexSidebar")); const NotificationsPanel = lazyWithPreload(() => import("./NotificationsPanel")); const Files = lazyWithPreload(() => import("./Files")); const FilesRail = lazyWithPreload(() => import("./FilesRail")); -const DownloadCenter = lazyWithPreload(() => import("./DownloadCenter")); const Stats = lazyWithPreload(() => import("./Stats")); const Scheduler = lazyWithPreload(() => import("./Scheduler")); const ConfigPanel = lazyWithPreload(() => import("./ConfigPanel")); @@ -57,7 +56,6 @@ export const PAGE_CONTENT: Record = { plex: { Component: PlexBrowse, maxWidth: "100rem" }, notifications: { Component: NotificationsPanel, maxWidth: "48rem" }, files: { Component: Files, maxWidth: "80rem" }, - downloads: { Component: DownloadCenter, maxWidth: "56rem" }, stats: { Component: Stats }, scheduler: { Component: Scheduler }, config: { Component: ConfigPanel }, diff --git a/frontend/src/components/navigation.dom.test.ts b/frontend/src/components/navigation.dom.test.ts index 380693a6..5306a247 100644 --- a/frontend/src/components/navigation.dom.test.ts +++ b/frontend/src/components/navigation.dom.test.ts @@ -69,13 +69,23 @@ describe("loadInitialPage precedence", () => { // whatever you happened to close the app on is precisely the habit the anti-paralysis surface // exists to replace, so the remembered page only wins once the account opts out. it("a bare URL lands on Home even when another module is remembered", () => { - stored[LS.page] = "downloads"; + stored[LS.page] = "plex"; expect(loadInitialPage()).toBe("home"); }); it("opting out restores the account's stored landing page", () => { + stored[LS.startOnHome] = START_ON_HOME.off; + stored[LS.page] = "plex"; + expect(loadInitialPage()).toBe("plex"); + }); + // The Downloads page is gone; what it held is in Files. A bookmark and a remembered page both land + // there instead of on the feed. + it("a retired page lands where its content went — linked or remembered", () => { + setUrl("?page=downloads"); + expect(loadInitialPage()).toBe("files"); + setUrl("/"); stored[LS.startOnHome] = START_ON_HOME.off; stored[LS.page] = "downloads"; - expect(loadInitialPage()).toBe("downloads"); + expect(loadInitialPage()).toBe("files"); }); it("opted out with no / invalid stored page still defaults to feed", () => { stored[LS.startOnHome] = START_ON_HOME.off; diff --git a/frontend/src/components/navigationInit.ts b/frontend/src/components/navigationInit.ts index abc8a2d9..2db279ca 100644 --- a/frontend/src/components/navigationInit.ts +++ b/frontend/src/components/navigationInit.ts @@ -2,7 +2,7 @@ // provider file keeps react-refresh happy (component-only exports). `loadInitialPage` picks the landing // module from a bookmark's params; `channelViewFrom` is the shared _chan/?chan resolver behind BOTH the // channelView seed and the onPop handler. -import { hasFilterParams, isPage, readPage, type Page } from "../lib/urlState"; +import { hasFilterParams, readPage, resolvePage, type Page } from "../lib/urlState"; import { getAccountRaw, LS } from "../lib/storage"; export const PAGE_KEY = LS.page; @@ -36,8 +36,7 @@ export function loadInitialPage(): Page { // replace. The account can opt out (Settings → Appearance → Startup) and get the remembered // module back. if (getAccountRaw(START_HOME_KEY) !== START_ON_HOME.off) return "home"; - const stored = getAccountRaw(PAGE_KEY); - return isPage(stored) ? stored : "feed"; + return resolvePage(getAccountRaw(PAGE_KEY)) ?? "feed"; } /** Resolve the open channel page from a landed history entry: the in-app `_chan` id (with its optional diff --git a/frontend/src/components/ui/DataTable.cards.dom.test.tsx b/frontend/src/components/ui/DataTable.cards.dom.test.tsx new file mode 100644 index 00000000..abf57aef --- /dev/null +++ b/frontend/src/components/ui/DataTable.cards.dom.test.tsx @@ -0,0 +1,46 @@ +// @vitest-environment jsdom +// +// Below `md` a table is a list of cards, its columns flowing one after another. A column marked +// `cardRow` takes a line of its own there — a row's lone "…" otherwise landed at the end of one +// card's line and on the next line of another. +import { describe, it, expect, afterEach, vi } from "vitest"; +import { render, cleanup, screen } from "@testing-library/react"; +import DataTable, { type Column } from "./DataTable"; + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), +})); + +const narrow = (matches: boolean) => + vi.stubGlobal( + "matchMedia", + (query: string) => + ({ + matches, + media: query, + addEventListener: () => {}, + removeEventListener: () => {}, + }) as unknown as MediaQueryList, + ); + +afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); +}); + +type Row = { id: string }; +const columns: Column[] = [ + { key: "name", header: "Name", cardPrimary: true, render: (r) => r.id }, + { key: "size", header: "Size", render: () => "1 MB" }, + { key: "menu", header: "", cardLabel: false, cardRow: true, render: () => }, +]; + +describe("DataTable cards", () => { + it("gives a cardRow column a line of its own; the others flow", () => { + narrow(false); + render( r.id} />); + expect(screen.getByText("more").parentElement!.className).toContain("basis-full"); + expect(screen.getByText("1 MB").parentElement!.className).not.toContain("basis-full"); + }); +}); diff --git a/frontend/src/components/ui/DataTable.pager.dom.test.tsx b/frontend/src/components/ui/DataTable.pager.dom.test.tsx new file mode 100644 index 00000000..49d7621a --- /dev/null +++ b/frontend/src/components/ui/DataTable.pager.dom.test.tsx @@ -0,0 +1,39 @@ +// @vitest-environment jsdom +// +// A short list that never outgrows a screen opts out of paging: every row, and no pager — not even +// the rows-per-page choice, which a paged table shows whenever it has rows. A size remembered for +// the same table does not bring paging back. +import { describe, it, expect, afterEach, vi } from "vitest"; +import { render, cleanup, screen } from "@testing-library/react"; +import DataTable, { type Column } from "./DataTable"; + +vi.mock("react-i18next", async (importOriginal) => ({ + ...(await importOriginal()), + useTranslation: () => ({ i18n: { language: "en" }, t: (key: string) => key }), +})); + +afterEach(() => { + cleanup(); + localStorage.clear(); +}); + +type Row = { id: string }; +const columns: Column[] = [{ key: "id", header: "Id", render: (r) => r.id }]; +const rows: Row[] = Array.from({ length: 12 }, (_, i) => ({ id: `row ${i + 1}` })); + +describe("DataTable pager", () => { + it("pages by default, rows-per-page shown", () => { + render( r.id} pageSize={10} />); + expect(screen.queryByText("row 11")).toBeNull(); + expect(screen.getByText("datatable.rowsPerPage")).toBeTruthy(); + }); + + it("pager={false} shows every row and no pager, whatever size was remembered", () => { + localStorage.setItem("t", JSON.stringify({ size: 10, page: 0 })); + render( + r.id} pager={false} persistKey="t" />, + ); + expect(screen.getByText("row 12")).toBeTruthy(); + expect(screen.queryByText("datatable.rowsPerPage")).toBeNull(); + }); +}); diff --git a/frontend/src/components/ui/DataTable.tsx b/frontend/src/components/ui/DataTable.tsx index b258c389..af8f6045 100644 --- a/frontend/src/components/ui/DataTable.tsx +++ b/frontend/src/components/ui/DataTable.tsx @@ -94,6 +94,7 @@ export default function DataTable({ rowKey, pageSize = 10, pageSizeOptions = [10, 25, 50, 100], + pager = true, persistKey, emptyText, rowClassName, @@ -119,6 +120,10 @@ export default function DataTable({ rowKey: (row: T) => string; pageSize?: number; pageSizeOptions?: number[]; + // A short list that never outgrows a screen (a user's download formats) has nothing to page: false + // shows every row and no pager — not even the rows-per-page choice, which there would only be + // clutter. It overrides `pageSize` and a persisted size, so the two cannot disagree. + pager?: boolean; persistKey?: string; emptyText?: string; rowClassName?: (row: T) => string; @@ -191,7 +196,8 @@ export default function DataTable({ const [internalFilters, setInternalFilters] = useState(initial.filters); const filters = controlledFilters ? (filtersProp ?? EMPTY_FILTERS) : internalFilters; const [page, setPage] = useState(initial.page); - const [size, setSize] = useState(initial.size ?? pageSize); + // 0 = "All": an unpaged table is one page of every row. + const [size, setSize] = useState(pager ? (initial.size ?? pageSize) : 0); const wide = useMediaQuery("(min-width: 48rem)"); const bodyRef = useRef(null); const cardsRef = useRef(null); @@ -315,20 +321,21 @@ export default function DataTable({ // Stays NULLABLE: the wrappers below decide whether to render a controls row at all, and an always // -truthy element would leave an empty `my-3` band (and an empty in-band PageToolbar) on a table // with nothing to page. The emptiness rule itself lives in Pager, so the two can't drift apart. - const pagerNode = hasPagerContent(sorted.length, size) ? ( - { - setSize(s); - setPage(0); - }} - sizeLabel={t("datatable.rowsPerPage")} - /> - ) : null; + const pagerNode = + pager && hasPagerContent(sorted.length, size) ? ( + { + setSize(s); + setPage(0); + }} + sizeLabel={t("datatable.rowsPerPage")} + /> + ) : null; const controls = controlsLeading != null || pagerNode ? ( @@ -424,7 +431,10 @@ export default function DataTable({ {columns .filter((c) => !c.hideInCard && !c.cardPrimary) .map((col) => ( - + {col.cardLabel !== false && ( {col.header} )} diff --git a/frontend/src/components/ui/pressableSelectable.contract.test.ts b/frontend/src/components/ui/pressableSelectable.contract.test.ts index 177962f7..0ee75241 100644 --- a/frontend/src/components/ui/pressableSelectable.contract.test.ts +++ b/frontend/src/components/ui/pressableSelectable.contract.test.ts @@ -45,7 +45,6 @@ const PLAIN: Record = { "UploadDialog.tsx": [1, "the drop zone"], "VideoEditor.tsx": [2, "timeline glyphs"], "Welcome.tsx": [1, "a screenshot (zoom)"], - "downloadRow.tsx": [1, "the error-details link"], "playlistColumns.tsx": [1, "the thumbnail"], "plexDetailUi.tsx": [2, "a switch row and the poster"], "plexViews.tsx": [1, "the rating filter (a number)"], diff --git a/frontend/src/i18n/locales/en/downloads.json b/frontend/src/i18n/locales/en/downloads.json index 16bdb39d..2bd0e537 100644 --- a/frontend/src/i18n/locales/en/downloads.json +++ b/frontend/src/i18n/locales/en/downloads.json @@ -1,5 +1,4 @@ { - "navLabel": "Downloads", "menu": { "download": "Download…" }, @@ -19,9 +18,7 @@ "manage": "Manage formats…" }, "page": { - "paste": "Paste link from clipboard", - "upload": "Upload", - "manage": "Manage formats…" + "paste": "Paste link from clipboard" }, "upload": { "title": "Upload a file", @@ -37,10 +34,6 @@ "addedConverting": "File uploaded — converting it so it plays on any phone", "failed": "Couldn't upload the file" }, - "tabs": { - "queue": "Queue", - "system": "System" - }, "status": { "queued": "Queued", "running": "Downloading", @@ -69,7 +62,6 @@ "retry": "Retry", "download": "Save to my device", "play": "Play", - "rename": "Rename", "edit": "Edit / trim…", "redownload": "Re-download (rebuild file)", "removeShared": "Remove from my list" @@ -78,12 +70,8 @@ "unsupported": "Your browser can't play this file." }, "empty": { - "queue": "Nothing in the queue.", "admin": "No downloads on this instance yet." }, - "source": { - "open": "Open the original page in a new tab" - }, "edit": { "title": "Edit details", "channel": "Channel", @@ -157,7 +145,6 @@ "profiles": { "title": "Download formats", "builtin": "Built-in", - "yours": "Your formats", "new": "New format", "name": "Name", "namePlaceholder": "My format", @@ -175,7 +162,17 @@ "save": "Save", "create": "Create", "delete": "Delete", - "deleteConfirm": "Delete the format “{{name}}”?" + "deleteConfirm": "Delete the format “{{name}}”?", + "edit": "Edit", + "duplicate": "Duplicate", + "saves": "What it saves", + "rowMenu": "Format actions", + "short": { + "subs": "subtitles", + "chapters": "chapters", + "thumbnail": "thumbnail", + "sponsorblock": "SponsorBlock" + } }, "admin": { "filesOnDisk": "Files on disk", @@ -192,10 +189,27 @@ "reset": "Reset to default", "save": "Save", "quotaPickPlaceholder": "Set a user's quota…", - "noFootprint": "No downloads yet." + "noFootprint": "No downloads yet.", + "allDownloads": "Every download", + "columns": { + "user": "User", + "used": "Used", + "quota": "Of quota", + "files": "Files", + "title": "Title", + "status": "Status", + "size": "Size" + }, + "ofQuota": "of {{size}}", + "unlimitedShort": "Unlimited", + "userMenu": "User storage actions", + "jobMenu": "Download actions", + "openSource": "Open source", + "editUserQuota": "Edit this user's quota", + "resetTitle": "Reset the quota?", + "resetBody": "{{email}} goes back to the instance's default limits." }, "clipBadge": "Clip", - "rowMenu": "Download actions", "errors": { "quota": { "max_bytes": "You've used your download storage quota ({{size}} GB). Free up space first.", @@ -223,7 +237,6 @@ "title": "Couldn't download", "titleUpload": "The upload isn't ready", "titleEdit": "The clip isn't ready", - "detailsLink": "details", "detailsToggle": "Technical details", "upload": { "quota_exceeded": { diff --git a/frontend/src/i18n/locales/en/keys.json b/frontend/src/i18n/locales/en/keys.json index af882d3e..40e630af 100644 --- a/frontend/src/i18n/locales/en/keys.json +++ b/frontend/src/i18n/locales/en/keys.json @@ -28,7 +28,6 @@ "goPlex": "Go to Plex", "goNotifications": "Go to Notifications", "goFiles": "Go to Files", - "goDownloads": "Go to Downloads", "goStats": "Go to Stats", "goSettings": "Go to Settings", "search": "Jump to the search box", diff --git a/frontend/src/i18n/locales/en/nav.json b/frontend/src/i18n/locales/en/nav.json index 1146c97e..f8cc9142 100644 --- a/frontend/src/i18n/locales/en/nav.json +++ b/frontend/src/i18n/locales/en/nav.json @@ -14,7 +14,6 @@ "plex": "Plex", "notifications": "Notifications", "files": "Files", - "downloads": "Downloads", "stats": "Stats", "scheduler": "Scheduler", "config": "Config", diff --git a/frontend/src/i18n/locales/en/settings.json b/frontend/src/i18n/locales/en/settings.json index 64dd2144..9c3c097a 100644 --- a/frontend/src/i18n/locales/en/settings.json +++ b/frontend/src/i18n/locales/en/settings.json @@ -4,7 +4,8 @@ "appearance": "Appearance", "notifications": "Notifications", "account": "Account", - "diagnostics": "Diagnostics" + "diagnostics": "Diagnostics", + "downloads": "Downloads" }, "plexSync": { "title": "Plex watch sync", @@ -230,5 +231,9 @@ "clear": "Clear", "empty": "No event matches the filter.", "emptyAll": "No events yet." + }, + "downloads": { + "formats": "Download formats", + "formatsIntro": "The choices the download dialog offers. The built-in ones are fixed; duplicate one to make your own." } } diff --git a/frontend/src/i18n/locales/en/users.json b/frontend/src/i18n/locales/en/users.json index 11fec23c..78e8edd2 100644 --- a/frontend/src/i18n/locales/en/users.json +++ b/frontend/src/i18n/locales/en/users.json @@ -1,7 +1,8 @@ { "tabs": { "roles": "Users & roles", - "access": "Access requests" + "access": "Access requests", + "storage": "Storage" }, "pendingAccess": { "title": "Signed up, but still outside", diff --git a/frontend/src/i18n/locales/en/welcome.json b/frontend/src/i18n/locales/en/welcome.json index 32bbdc6c..bea41497 100644 --- a/frontend/src/i18n/locales/en/welcome.json +++ b/frontend/src/i18n/locales/en/welcome.json @@ -14,7 +14,7 @@ "channels": "Channels", "playlists": "Playlists", "player": "In-app player", - "downloads": "Downloads", + "files": "Your files", "search": "Search results", "plex": "Your Plex library" }, @@ -40,9 +40,9 @@ "title": "Watch in-app, resume anywhere", "body": "A built-in player picks each video up where you left off, with keyboard and scroll controls." }, - "downloads": { - "title": "Downloads", - "body": "Save videos to your server in a Plex-friendly layout, trim or join them, then share a link." + "files": { + "title": "Files", + "body": "Every file you keep in one library: paste a link anywhere to download it, trim or join a video, upload your own, then share a link that plays on any phone." }, "plex": { "title": "Your Plex library too", diff --git a/frontend/src/i18n/locales/hu/downloads.json b/frontend/src/i18n/locales/hu/downloads.json index 39e12648..85da3a2c 100644 --- a/frontend/src/i18n/locales/hu/downloads.json +++ b/frontend/src/i18n/locales/hu/downloads.json @@ -1,5 +1,4 @@ { - "navLabel": "Letöltések", "menu": { "download": "Letöltés…" }, @@ -19,9 +18,7 @@ "manage": "Formátumok kezelése…" }, "page": { - "paste": "Link beillesztése a vágólapról", - "upload": "Feltöltés", - "manage": "Formátumok kezelése…" + "paste": "Link beillesztése a vágólapról" }, "upload": { "title": "Fájl feltöltése", @@ -37,10 +34,6 @@ "addedConverting": "Fájl feltöltve — átalakítjuk, hogy minden telefonon lejátszható legyen", "failed": "Nem sikerült feltölteni a fájlt" }, - "tabs": { - "queue": "Sor", - "system": "Rendszer" - }, "status": { "queued": "Várakozik", "running": "Letöltés", @@ -69,7 +62,6 @@ "retry": "Újra", "download": "Mentés a gépemre", "play": "Lejátszás", - "rename": "Átnevezés", "edit": "Szerkesztés / vágás…", "redownload": "Újraletöltés (fájl újraépítése)", "removeShared": "Eltávolítás a listámból" @@ -78,12 +70,8 @@ "unsupported": "A böngésződ nem tudja lejátszani ezt a fájlt." }, "empty": { - "queue": "A sor üres.", "admin": "Ezen a példányon még nincs letöltés." }, - "source": { - "open": "Eredeti oldal megnyitása új lapon" - }, "edit": { "title": "Adatok szerkesztése", "channel": "Csatorna", @@ -157,7 +145,6 @@ "profiles": { "title": "Letöltési formátumok", "builtin": "Beépített", - "yours": "Saját formátumaid", "new": "Új formátum", "name": "Név", "namePlaceholder": "Saját formátum", @@ -175,7 +162,17 @@ "save": "Mentés", "create": "Létrehozás", "delete": "Törlés", - "deleteConfirm": "Törlöd a(z) „{{name}}” formátumot?" + "deleteConfirm": "Törlöd a(z) „{{name}}” formátumot?", + "edit": "Szerkesztés", + "duplicate": "Másolat készítése", + "saves": "Mit ment", + "rowMenu": "Formátum-műveletek", + "short": { + "subs": "felirat", + "chapters": "fejezetek", + "thumbnail": "bélyegkép", + "sponsorblock": "SponsorBlock" + } }, "admin": { "filesOnDisk": "Fájlok a lemezen", @@ -192,10 +189,27 @@ "reset": "Visszaállítás alapértékre", "save": "Mentés", "quotaPickPlaceholder": "Kvóta beállítása egy felhasználónak…", - "noFootprint": "Még nincs letöltés." + "noFootprint": "Még nincs letöltés.", + "allDownloads": "Minden letöltés", + "columns": { + "user": "Felhasználó", + "used": "Foglalt", + "quota": "A kvótából", + "files": "Fájlok", + "title": "Cím", + "status": "Állapot", + "size": "Méret" + }, + "ofQuota": "{{size}}-ból", + "unlimitedShort": "Korlátlan", + "userMenu": "Tárhely-műveletek", + "jobMenu": "Letöltés-műveletek", + "openSource": "Forrás megnyitása", + "editUserQuota": "A felhasználó kvótájának szerkesztése", + "resetTitle": "Visszaállítod a kvótát?", + "resetBody": "{{email}} visszakapja a példány alapértelmezett korlátait." }, "clipBadge": "Klip", - "rowMenu": "Letöltés műveletei", "errors": { "quota": { "max_bytes": "Elfogyott a letöltési tárhelykereted ({{size}} GB). Előbb szabadíts fel helyet.", @@ -223,7 +237,6 @@ "title": "Nem sikerült letölteni", "titleUpload": "A feltöltés nem készült el", "titleEdit": "A klip nem készült el", - "detailsLink": "részletek", "detailsToggle": "Technikai részletek", "upload": { "quota_exceeded": { diff --git a/frontend/src/i18n/locales/hu/keys.json b/frontend/src/i18n/locales/hu/keys.json index e4f9a689..d6282014 100644 --- a/frontend/src/i18n/locales/hu/keys.json +++ b/frontend/src/i18n/locales/hu/keys.json @@ -28,7 +28,6 @@ "goPlex": "Ugrás: Plex", "goNotifications": "Ugrás: Értesítések", "goFiles": "Ugrás a Fájlokhoz", - "goDownloads": "Ugrás: Letöltések", "goStats": "Ugrás: Statisztika", "goSettings": "Ugrás: Beállítások", "search": "Ugrás a keresőbe", diff --git a/frontend/src/i18n/locales/hu/nav.json b/frontend/src/i18n/locales/hu/nav.json index 1c4391ac..1300d07a 100644 --- a/frontend/src/i18n/locales/hu/nav.json +++ b/frontend/src/i18n/locales/hu/nav.json @@ -14,7 +14,6 @@ "plex": "Plex", "notifications": "Értesítések", "files": "Fájlok", - "downloads": "Letöltések", "stats": "Statisztika", "scheduler": "Ütemező", "config": "Konfig", diff --git a/frontend/src/i18n/locales/hu/settings.json b/frontend/src/i18n/locales/hu/settings.json index bcfada78..4be96686 100644 --- a/frontend/src/i18n/locales/hu/settings.json +++ b/frontend/src/i18n/locales/hu/settings.json @@ -4,7 +4,8 @@ "appearance": "Megjelenés", "notifications": "Értesítések", "account": "Fiók", - "diagnostics": "Diagnosztika" + "diagnostics": "Diagnosztika", + "downloads": "Letöltések" }, "plexSync": { "title": "Plex nézettség-szinkron", @@ -230,5 +231,9 @@ "clear": "Ürítés", "empty": "Nincs a szűrésnek megfelelő esemény.", "emptyAll": "Még nincs esemény." + }, + "downloads": { + "formats": "Letöltési formátumok", + "formatsIntro": "Ezek közül választhatsz a letöltés ablakában. A beépítettek nem módosíthatók; másold le valamelyiket, ha sajátot szeretnél." } } diff --git a/frontend/src/i18n/locales/hu/users.json b/frontend/src/i18n/locales/hu/users.json index 1e4330fe..57b6666e 100644 --- a/frontend/src/i18n/locales/hu/users.json +++ b/frontend/src/i18n/locales/hu/users.json @@ -1,7 +1,8 @@ { "tabs": { "roles": "Felhasználók és szerepek", - "access": "Hozzáférési kérelmek" + "access": "Hozzáférési kérelmek", + "storage": "Tárhely" }, "pendingAccess": { "title": "Regisztrált, de még kívül van", diff --git a/frontend/src/i18n/locales/hu/welcome.json b/frontend/src/i18n/locales/hu/welcome.json index 568176e8..39c46901 100644 --- a/frontend/src/i18n/locales/hu/welcome.json +++ b/frontend/src/i18n/locales/hu/welcome.json @@ -14,7 +14,7 @@ "channels": "Csatornák", "playlists": "Lejátszási listák", "player": "Beépített lejátszó", - "downloads": "Letöltések", + "files": "A fájljaid", "search": "Keresési találatok", "plex": "A Plex-könyvtárad" }, @@ -40,9 +40,9 @@ "title": "Nézd appon belül, folytasd bárhonnan", "body": "A beépített lejátszó onnan folytatja a videót, ahol abbahagytad — billentyű- és görgetés-vezérléssel." }, - "downloads": { - "title": "Letöltések", - "body": "Ments videókat a szerveredre Plex-barát elrendezésben, vágd vagy fűzd össze őket, majd oszd meg egy linkkel." + "files": { + "title": "Fájlok", + "body": "Minden megtartott fájlod egy könyvtárban: illessz be bárhová egy linket, és letölti; vágj vagy fűzz össze videót, tölts fel sajátot, majd ossz meg olyan linket, ami bármelyik telefonon lejátszható." }, "plex": { "title": "A Plex-könyvtárad is", diff --git a/frontend/src/lib/api/downloads.ts b/frontend/src/lib/api/downloads.ts index a12d4586..da3aeaee 100644 --- a/frontend/src/lib/api/downloads.ts +++ b/frontend/src/lib/api/downloads.ts @@ -112,6 +112,8 @@ export interface DownloadJob { expires_at: string | null; asset: DownloadAsset | null; shared?: boolean; + // Whose it is — only the admin Storage tab's listing of every user's downloads carries them. + user_id?: number; user_email?: string; // Where the viewer stopped in it (whole seconds; 0 = from the start). Only the two library listings // (`downloads`, `downloadsShared`) carry it — a job a mutation returns does not. @@ -147,12 +149,23 @@ export interface ShareLink { // languages is exactly the drift that CLAUDE.md's comment-vs-code rule is about. export const DISPLAY_NAME_MAX = 255; +/** One user's row on the admin Storage tab: their footprint against their own limit, and how many + * files their Files lists. */ +export interface AdminStorageUser { + user_id: number; + email: string | null; + footprint_bytes: number; + files: number; + max_bytes: number; + unlimited: boolean; +} + export interface AdminStorage { files_on_disk: number; total_bytes: number; total_assets: number; total_cap_bytes: number; - per_user: { user_id: number; email: string | null; footprint_bytes: number }[]; + per_user: AdminStorageUser[]; } export interface AdminDownloadQuota { diff --git a/frontend/src/lib/downloadQueue.ts b/frontend/src/lib/downloadQueue.ts index f59a605d..6e6133a6 100644 --- a/frontend/src/lib/downloadQueue.ts +++ b/frontend/src/lib/downloadQueue.ts @@ -4,9 +4,9 @@ import { createStore } from "./store"; // The download queue as this tab has watched it: which jobs it has seen finish. The tray shows them // ("Done · Open") until it is closed, and announces each once (the done notice). // -// Watched where the queue is FETCHED, not in a component's effect: every reader of the queue (the -// tray, the Downloads page) fetches through `fetchQueue`, so a finish is seen once whichever of them -// polled, and nothing has to diff the previous render's data. +// Watched where the queue is FETCHED, not in a component's effect: every reader of the queue fetches +// through `fetchQueue`, so a finish is seen once whichever of them polled, and nothing has to diff the +// previous render's data. /** The statuses a job leaves on its own: it is waiting, or working. */ export const IN_FLIGHT: ReadonlySet = new Set(["queued", "running", "paused"]); @@ -14,6 +14,13 @@ export const IN_FLIGHT: ReadonlySet = new Set(["queued", "running", "pau let lastSeen: Map | null = null; const finished = createStore([]); const unannounced = createStore([]); +const revealing = createStore(null); + +/** A job to show in the tray, asked for at `at` (ms). */ +export interface Reveal { + id: number; + at: number; +} /** Note a fresh queue: a job that is done now and was not on the last look has just finished. The * first look only learns what is there — a file finished before this tab opened is old news. */ @@ -51,9 +58,21 @@ export function takeUnannounced(): QueueJob[] { return list; } +/** "View" on a job just started (a download, a clip, an upload still converting): the tray opens on + * its row — unfolded, back if it was closed, the keyboard on the row. One the tray no longer lists + * because it is done already opens as a file. */ +export const showInTray = (id: number): void => revealing.set({ id, at: Date.now() }); + +/** The pending "View", until the tray has answered it. */ +export const useRevealing = (): Reveal | null => revealing.useValue(); + +/** The tray answered the "View" (or the job is not in the queue at all). */ +export const doneRevealing = (): void => revealing.set(null); + /** Forget what was watched (a test starts from a tab that has looked at nothing). */ export function resetQueueWatch(): void { lastSeen = null; finished.set([]); unannounced.set([]); + revealing.set(null); } diff --git a/frontend/src/lib/format.ts b/frontend/src/lib/format.ts index 11669904..9fe40ca6 100644 --- a/frontend/src/lib/format.ts +++ b/frontend/src/lib/format.ts @@ -45,12 +45,6 @@ export function formatBytes(bytes: number | null | undefined, lang: string): str return `${num} ${units[i]}`; } -/** Download speed from bytes/sec, e.g. "2.4 MB/s". */ -export function formatSpeed(bps: number | null | undefined, lang: string): string { - if (!bps) return ""; - return `${formatBytes(bps, lang)}/s`; -} - /** Relative-time tiers as [seconds-per-unit, Intl unit]. The unit is what the divisor produces, * and Intl.RelativeTimeFormat turns the pair into the localized words — so a divisor can no * longer be paired with the wrong label, which is exactly what shifted every tier by one from diff --git a/frontend/src/lib/keymap.sequence.test.ts b/frontend/src/lib/keymap.sequence.test.ts index 7d43cb1c..03ac5673 100644 --- a/frontend/src/lib/keymap.sequence.test.ts +++ b/frontend/src/lib/keymap.sequence.test.ts @@ -84,7 +84,7 @@ describe("two-step sequences", () => { dispatch(press({ key: "g" }), "page", pageHandlers(calls)); dispatch(press({ key: "Shift", shiftKey: true }), "page", pageHandlers(calls)); dispatch(press({ key: "d" }), "page", pageHandlers(calls)); - expect(calls).toEqual(["goDownloads"]); + expect(calls).toEqual(["goFiles"]); }); // No second step needs a modifier (see the page-level rows below), so a chord with one is the reader changing their mind: it diff --git a/frontend/src/lib/keymap.ts b/frontend/src/lib/keymap.ts index 5bf8219f..22409639 100644 --- a/frontend/src/lib/keymap.ts +++ b/frontend/src/lib/keymap.ts @@ -117,14 +117,8 @@ export const SHORTCUTS = [ labelKey: "keys.goNotifications", page: "notifications", }, - { id: "goFiles", scope: "page", keys: ["g l"], labelKey: "keys.goFiles", page: "files" }, - { - id: "goDownloads", - scope: "page", - keys: ["g d"], - labelKey: "keys.goDownloads", - page: "downloads", - }, + // "g d" is the retired Downloads page's chord: what it held is in Files now, so it goes there too. + { id: "goFiles", scope: "page", keys: ["g l", "g d"], labelKey: "keys.goFiles", page: "files" }, { id: "goStats", scope: "page", keys: ["g s"], labelKey: "keys.goStats", page: "stats" }, { id: "goSettings", scope: "page", keys: ["g ,"], labelKey: "keys.goSettings", page: "settings" }, diff --git a/frontend/src/lib/modules.ts b/frontend/src/lib/modules.ts index 1937d084..9d727440 100644 --- a/frontend/src/lib/modules.ts +++ b/frontend/src/lib/modules.ts @@ -3,7 +3,6 @@ import { BarChart3, Bell, Clapperboard, - Download, Home, ListVideo, ScrollText, @@ -95,13 +94,6 @@ const MODULES: readonly ModuleDef[] = [ railKey: "nav.rail.files", icon: FolderOpen, }, - { - id: "downloads", - labelKey: "downloads.navLabel", - railKey: "nav.rail.downloads", - icon: Download, - hasBadge: true, - }, { id: "stats", labelKey: "header.account.stats", diff --git a/frontend/src/lib/nav.ts b/frontend/src/lib/nav.ts index c008b50b..e985bfdf 100644 --- a/frontend/src/lib/nav.ts +++ b/frontend/src/lib/nav.ts @@ -1,21 +1,6 @@ import type { Page } from "./urlState"; -// Tiny decoupled navigator so deep components (a feed card's download toast, the Download -// Center) can switch pages without threading setPage through the whole tree. NavigationProvider -// registers its setPage on mount; navigateTo is a no-op until then. -let _navigate: ((p: Page) => void) | null = null; - -export function setNavigator(fn: (p: Page) => void): void { - _navigate = fn; -} - -/** The "View" on a "Download added" toast is clicked INSIDE the player (its toast host). That is - * safe because `setPage` closes every open overlay before it pushes (see - * `lib/history.closeOverlaysThenNavigate`) — without it the player's Back-to-close cleanup popped the - * entry the navigation had just pushed. */ -export function navigateTo(p: Page): void { - _navigate?.(p); -} +// Page links: a page's own address, and whether a click on one is the app's to handle. /** A page's own address, for a link to it (the nav rail's rows). Only the page: the rest of a page's * state — the feed's filters, Plex's library — a fresh tab restores from the account's own storage, diff --git a/frontend/src/lib/pageKeys.dom.test.tsx b/frontend/src/lib/pageKeys.dom.test.tsx index 526b6c9c..07d4507d 100644 --- a/frontend/src/lib/pageKeys.dom.test.tsx +++ b/frontend/src/lib/pageKeys.dom.test.tsx @@ -22,7 +22,7 @@ afterEach(() => { vi.useRealTimers(); }); -const ALL: Page[] = ["home", "feed", "channels", "playlists", "plex", "downloads", "settings"]; +const ALL: Page[] = ["home", "feed", "channels", "playlists", "plex", "files", "settings"]; function Host({ onNavigate, @@ -61,6 +61,15 @@ describe("the g chords", () => { expect(nav.mock.calls).toEqual([["feed"], ["settings"]]); }); + // The retired Downloads page's chord still answers — where its content went. + it("g d, the old Downloads chord, goes to Files", () => { + const nav = vi.fn(); + render(); + key({ key: "g" }); + key({ key: "d" }); + expect(nav.mock.calls).toEqual([["files"]]); + }); + // `h` alone opens the sheet; the `h` of `g h` must not ALSO do that. it("take the h of g h away from the help sheet", () => { const nav = vi.fn(); diff --git a/frontend/src/lib/pageMeta.test.ts b/frontend/src/lib/pageMeta.test.ts index dff34685..67d4b80a 100644 --- a/frontend/src/lib/pageMeta.test.ts +++ b/frontend/src/lib/pageMeta.test.ts @@ -12,7 +12,6 @@ const EXPECTED: Record = { playlists: "header.account.playlists", notifications: "inbox.navLabel", files: "files.navLabel", - downloads: "downloads.navLabel", stats: "header.usageStats", plex: "plex.navLabel", scheduler: "header.scheduler", diff --git a/frontend/src/lib/storage.ts b/frontend/src/lib/storage.ts index 360e846e..9f4c4bed 100644 --- a/frontend/src/lib/storage.ts +++ b/frontend/src/lib/storage.ts @@ -26,6 +26,9 @@ export const LS = { // The queue the reader closed the tray on (job ids, a failure marked) — it stays shut until that // changes. Per-account mirror of the server pref. downloadTrayClosed: "siftlode.downloadTrayClosed", + // The download format used last — the download dialog opens on it. Per-account mirror of the + // server pref. + downloadProfile: "siftlode.downloadProfile", hints: "siftlode.hints", lang: "siftlode.lang", filters: "siftlode.filters", diff --git a/frontend/src/lib/tableColumns.ts b/frontend/src/lib/tableColumns.ts index e464eb28..e4e10cc7 100644 --- a/frontend/src/lib/tableColumns.ts +++ b/frontend/src/lib/tableColumns.ts @@ -37,6 +37,9 @@ export interface Column { cardPrimary?: boolean; hideInCard?: boolean; cardLabel?: boolean; + // …and `cardRow` gives the value a line of its own: a row's lone "…" flowed in after the facts, + // at the end of one card's line and on the next line of another — never in the same place twice. + cardRow?: boolean; // A column a table WITHOUT the card fallback (the playlist's drag-reorderable hybrid) leaves out // while the TABLE is narrower than 42rem: its fixed widths alone overran the box and left the title // no width. The table's own width, not the viewport's (an `@container` round it): beside the diff --git a/frontend/src/lib/urlState.ts b/frontend/src/lib/urlState.ts index 8dfdb02d..09a62083 100644 --- a/frontend/src/lib/urlState.ts +++ b/frontend/src/lib/urlState.ts @@ -132,20 +132,32 @@ const PAGES = [ "logs", "notifications", "files", - "downloads", "plex", ] as const; export type Page = (typeof PAGES)[number]; /** Narrow an arbitrary string to a known Page (else null). */ -export function isPage(p: string | null | undefined): p is Page { +function isPage(p: string | null | undefined): p is Page { return !!p && (PAGES as readonly string[]).includes(p); } +// A page this build no longer has, and the page that took its place. A bookmark, the remembered last +// page and an old history entry can still name one; all three resolve through `resolvePage`, so a +// retired page lands where its content went instead of on the feed. +const RETIRED: Readonly> = { + // The download queue became the tray; what it downloaded lives in Files. + downloads: "files", +}; + +/** A page id as stored or linked: a live page, a retired page's successor, else null. */ +export function resolvePage(p: string | null | undefined): Page | null { + if (isPage(p)) return p; + return p && Object.prototype.hasOwnProperty.call(RETIRED, p) ? RETIRED[p]! : null; +} + export function readPage(): Page { - const p = new URLSearchParams(window.location.search).get("page"); - return isPage(p) ? p : "feed"; + return resolvePage(new URLSearchParams(window.location.search).get("page")) ?? "feed"; } /** Build a URL (path + query + fragment) from the CURRENT location with `updates` applied to the query —