Files
siftlode/frontend/src/lib/panelLayout.ts
T
peter 85aa917b38 feat(files): the Files page — the library in one aligned list, its folders in a rail
Files is the one library: every done file, the ones shared with me, or one
folder's, picked in a side rail (sources, own folders, the storage meter) and
mirrored to ?folder=. One table with every kind of value in its own column;
Copy link, Share and Save in sight, every action on a right click: play,
cut, rename in the row, move to an own folder or back home, keep, details,
re-download, delete.

The Downloads page keeps only the queue until the tray replaces it. The old
/api/downloads library routes are gone: list shared, upload, the details
PATCH, delete, the file and the usage now live under /api/files, and a
listed file says which source it came from. The job's "expired" flag is gone
with the state it described.
2026-10-11 05:34:25 +02:00

88 lines
2.9 KiB
TypeScript

// Per-user customization of a side panel's group cards: their order, which are collapsed, and
// which are hidden — the same "customize" affordance the feed filter rail had, generalized so
// every panel (feed / plex / playlists / files) has its own saved layout. Persisted to localStorage and
// the server-side `preferences` blob (schemaless), so it follows the account across devices.
import { LS, readAccount, writeAccount } from "./storage";
export type PanelId = "feed" | "plex" | "playlists" | "files";
export interface PanelLayout {
order: string[];
collapsed: Record<string, boolean>;
hidden: Record<string, boolean>;
}
// The default group order per panel. Group ids must match the ids each panel feeds to PanelGroups.
// Unknown/stale ids are dropped and any missing (e.g. a group added in a later version) are
// appended by normalizeLayout, so nothing silently disappears.
const PANEL_ORDER: Record<PanelId, string[]> = {
feed: ["date", "year", "channel", "tags", "language", "topic"],
plex: [
"playlists",
"watch",
"people",
"collection",
"rating",
"genre",
"year",
"duration",
"added",
"contentrating",
],
playlists: ["manage", "list"],
files: ["library", "sources", "own"],
};
// localStorage key per panel (feed keeps its original key for backward compatibility).
const LS_KEY: Record<PanelId, string> = {
feed: LS.sidebarLayout,
plex: LS.plexLayout,
playlists: LS.playlistsLayout,
files: LS.filesLayout,
};
// Key on the server `preferences` blob per panel (feed keeps its original key).
export const PREF_KEY: Record<
PanelId,
"sidebarLayout" | "plexLayout" | "playlistsLayout" | "filesLayout"
> = {
feed: "sidebarLayout",
plex: "plexLayout",
playlists: "playlistsLayout",
files: "filesLayout",
};
export function defaultLayout(panel: PanelId): PanelLayout {
return { order: [...PANEL_ORDER[panel]], collapsed: {}, hidden: {} };
}
function normalizeLayout(panel: PanelId, raw: unknown): PanelLayout {
const all = PANEL_ORDER[panel];
const r = (raw ?? {}) as Partial<PanelLayout>;
const order = Array.isArray(r.order) ? r.order.filter((x) => all.includes(x)) : [];
for (const id of all) if (!order.includes(id)) order.push(id);
return {
order,
collapsed: { ...(r.collapsed ?? {}) },
hidden: { ...(r.hidden ?? {}) },
};
}
function loadLayout(panel: PanelId): PanelLayout {
return normalizeLayout(panel, readAccount<unknown>(LS_KEY[panel], {}));
}
// A panel's layout when the app mounts: the account's server copy (the `me` cache's preferences) when
// it has one — so a layout changed on another device is right on the first paint — else this browser's.
export function seedLayout(
panel: PanelId,
prefs: Record<string, unknown> | undefined,
): PanelLayout {
const raw = prefs?.[PREF_KEY[panel]];
return raw ? normalizeLayout(panel, raw) : loadLayout(panel);
}
export function saveLayoutLocal(panel: PanelId, l: PanelLayout): void {
writeAccount(LS_KEY[panel], l);
}