- A library past the list's cap is searched and sorted on the server; the count says "the newest". - Each folder and each search opens on its first page (DataTable pageScope, useCardPager scope). - A file dropped where the page takes none never reaches the browser (which would unload the app). - The viewer's arrows wait while a layer opened from it is on top, and scroll a wide text line; a text shows its first 5000 lines and says where the rest is. - Confirms name the file (no more "your downloads"); the asking menu rows end in "…". - The tray names a running step beside its bar, shows a pause mark for paused work, polls slowly while only paused work waits, and forgets a finished file that was deleted. - An unchanged rename sends nothing; a paste opens the download dialog only with nothing on top; the link box never replaces what was typed. - Honest states: folders that failed to load, a file no longer there, the storage figure; the rail says "nothing downloaded" only when there is nothing. - Focus after deleting the open folder and when the format form opens; the format name's label. - ui/Meter (one danger line for both quota bars) and ui/RenameField in the platform layer. - Copy fixes, stale comments and the speculative hub key; a contract test for the list's parameters; e2e: the Files page and the paste intake.
34 lines
1.4 KiB
TypeScript
34 lines
1.4 KiB
TypeScript
import { Film, Folder, Globe, Upload, type LucideIcon } from "lucide-react";
|
|
import { YouTubeIcon } from "../lib/brandIcons";
|
|
import type { FileFolder } from "../lib/api";
|
|
|
|
// A library folder's and a file's origin, named and drawn. The backend sends a source folder's KEY
|
|
// (`youtube`, `plex`, `upload`, `site:<host>`, see backend downloads/folders.py) and leaves
|
|
// its name to the client, in the reader's language; an own folder carries the name its user gave it.
|
|
|
|
const SITE_PREFIX = "site:";
|
|
|
|
type T = (key: string) => string;
|
|
|
|
/** The name of a source: a site is its host, the rest are words of the UI. */
|
|
export function sourceLabel(key: string, t: T): string {
|
|
if (key.startsWith(SITE_PREFIX)) return key.slice(SITE_PREFIX.length);
|
|
if (key === "youtube" || key === "plex" || key === "upload") return t(`files.source.${key}`);
|
|
return key;
|
|
}
|
|
|
|
export function sourceIcon(key: string): LucideIcon | typeof YouTubeIcon {
|
|
if (key === "youtube") return YouTubeIcon;
|
|
if (key === "plex") return Film;
|
|
if (key === "upload") return Upload;
|
|
return Globe;
|
|
}
|
|
|
|
export function folderLabel(folder: FileFolder, t: T): string {
|
|
return folder.kind === "own" ? (folder.name ?? "") : sourceLabel(folder.source_key ?? "", t);
|
|
}
|
|
|
|
export function folderIcon(folder: FileFolder): LucideIcon | typeof YouTubeIcon {
|
|
return folder.kind === "own" ? Folder : sourceIcon(folder.source_key ?? "");
|
|
}
|