`filters.channelId` was settable by exactly one thing: a "?channel=" URL from a shared view. The panel still rendered a CHANNEL card whose only control was "remove" — a remove button for a filter the UI could not apply. Two ways in now. The panel gets a real Channel group (searchable picker, first-class alongside date/tags/language, so the chip row surfaces it for free); normalizeLayout appends it to saved layouts, so nobody loses their arrangement. And a subscribed channel's page gets "Show in my feed", which applies the filter and keeps the rest of your filters — the point is "this channel, the way I normally read", which the channel page itself can't answer (it runs show=all, scope=all over the whole catalog). The picker owns its channel query so a few-hundred-entry list loads when the panel is actually opened, not on every feed visit, and says "showing 50 of N" rather than quietly stopping.
74 lines
2.4 KiB
TypeScript
74 lines
2.4 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) 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";
|
|
|
|
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: ["savedviews", "date", "channel", "tags", "language", "topic"],
|
|
plex: [
|
|
"scope",
|
|
"playlists",
|
|
"watch",
|
|
"sort",
|
|
"people",
|
|
"collection",
|
|
"rating",
|
|
"genre",
|
|
"year",
|
|
"duration",
|
|
"added",
|
|
"contentrating",
|
|
],
|
|
playlists: ["manage", "list"],
|
|
};
|
|
|
|
// 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,
|
|
};
|
|
|
|
// Key on the server `preferences` blob per panel (feed keeps its original key).
|
|
export const PREF_KEY: Record<PanelId, "sidebarLayout" | "plexLayout" | "playlistsLayout"> = {
|
|
feed: "sidebarLayout",
|
|
plex: "plexLayout",
|
|
playlists: "playlistsLayout",
|
|
};
|
|
|
|
export function defaultLayout(panel: PanelId): PanelLayout {
|
|
return { order: [...PANEL_ORDER[panel]], collapsed: {}, hidden: {} };
|
|
}
|
|
|
|
export 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 ?? {}) },
|
|
};
|
|
}
|
|
|
|
export function loadLayout(panel: PanelId): PanelLayout {
|
|
return normalizeLayout(panel, readAccount<unknown>(LS_KEY[panel], {}));
|
|
}
|
|
|
|
export function saveLayoutLocal(panel: PanelId, l: PanelLayout): void {
|
|
writeAccount(LS_KEY[panel], l);
|
|
}
|