feat(files): the Downloads page goes — the tray, a Storage tab and a Downloads settings tab take its place
- 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.
This commit is contained in:
@@ -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<HTMLDivElement>(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<HTMLDivElement>(null);
|
||||
useLayoutEffect(() => {
|
||||
const el = box.current;
|
||||
if (!el) {
|
||||
|
||||
Reference in New Issue
Block a user