refactor(frontend): each module defines itself in its folder; rails load lazily and keep their own state
A page used to be listed three times (lib/modules for its label, icon and gate; shell/moduleRegistry for its page and rail; lib/pageMeta for its title), and App held every rail's layout and collapse flag. Now each module's `module.ts` says it all — label, icon, gate, its lazy page and rail, its width — handed out through its index.ts, and features/navigation/moduleRegistry lists them in nav order for the rail, the palette, the tab title, the reachable set and the shell's render. A contract test holds the registry to the page ids and each definition to its own folder, with no static import of a page. Every rail is lazy now, loaded together with its page so a pinned rail never arrives after it and shoves the content aside; the feed's and the playlists' rails took @dnd-kit off the boot path (boot 428.5 → 393.9 KiB gzip, the App chunk 122 → 32 KiB). A rail reads its own layout and collapse flag (lib/useRail) and writes a layout change to the `me` cache too, since it now mounts anew on each visit. App's session effects, its one-shot entry-URL handlers and the admin nudge move into shell hooks; App is layout. The registry's three layer suppressions are gone: none is left.
This commit is contained in:
@@ -8,7 +8,6 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { ListMusic, Plus, SlidersHorizontal } from "lucide-react";
|
||||
import { api, plexFilterCount, type PlexFilters } from "../../lib/api";
|
||||
import { notifyActionError } from "../../lib/actionErrors";
|
||||
import { type PanelLayout } from "../../lib/panelLayout";
|
||||
import { type MediaSourceKind } from "../../lib/mediaSource";
|
||||
import { useDebounced } from "../../lib/useDebounced";
|
||||
import FilterRail from "../../components/ui/FilterRail";
|
||||
@@ -22,19 +21,13 @@ import { useSavedViews } from "../../features/filters/savedViewsContext";
|
||||
import { usePlex, usePlexActions } from "./plexContext";
|
||||
import { DismissibleChip } from "../../components/ui/Chip";
|
||||
import { Input } from "../../components/ui/Input";
|
||||
import { useRail } from "../../lib/useRail";
|
||||
|
||||
// The Plex module's left filter column — now the shared floating SidePanel with its filter groups
|
||||
// as reorderable "islands" (same system as the feed rail). Movie libraries get the full metadata
|
||||
// set; shows keep library + sort. State is owned by App (per-account persisted); facets come from
|
||||
// set; shows keep library + sort. The filter state lives in PlexProvider; facets come from
|
||||
// the backend so the panel only offers what the library actually contains.
|
||||
|
||||
type Props = {
|
||||
layout: PanelLayout;
|
||||
setLayout: (l: PanelLayout) => void;
|
||||
collapsed: boolean;
|
||||
onToggleCollapse: () => void;
|
||||
};
|
||||
|
||||
const RATING_STEPS = [5, 6, 7, 8, 9];
|
||||
const ADDED_OPTS = ["24h", "1w", "1m", "6m", "1y"] as const;
|
||||
const DURATION_BUCKETS: { key: string; min: number | null; max: number | null }[] = [
|
||||
@@ -43,7 +36,9 @@ const DURATION_BUCKETS: { key: string; min: number | null; max: number | null }[
|
||||
{ key: "long", min: 120 * 60, max: null },
|
||||
];
|
||||
|
||||
export default function PlexSidebar({ layout, setLayout, collapsed, onToggleCollapse }: Props) {
|
||||
export default function PlexSidebar() {
|
||||
// Its layout and collapse flag are its own (lib/useRail), so the shell only places it.
|
||||
const { layout, setLayout, collapsed, onToggleCollapse } = useRail("plex");
|
||||
const { t, i18n } = useTranslation();
|
||||
// Scope / watch-state / sort / expanded filters live in PlexProvider (shared with PlexBrowse and
|
||||
// the header search box); this rail is the primary editor of them. Opening a playlist is a
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
export { PlexProvider } from "./PlexProvider";
|
||||
export { usePlex, usePlexActions } from "./plexContext";
|
||||
export { buildPlexActiveFilters } from "./usePlexActiveFilters";
|
||||
export { plexModule } from "./module";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { Clapperboard } from "lucide-react";
|
||||
import { lazyWithPreload, lazyWithPreloadAlongside } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
|
||||
// The rail loads with the page (lazyWithPreloadAlongside), so it never arrives after it.
|
||||
const Rail = lazyWithPreload(() => import("./PlexSidebar"));
|
||||
|
||||
// This page as the shell and the navigation know it (lib/moduleDef). Its page and rail load lazily.
|
||||
export const plexModule: ModuleDef = {
|
||||
id: "plex",
|
||||
labelKey: "plex.navLabel",
|
||||
railKey: "nav.rail.plex",
|
||||
icon: Clapperboard,
|
||||
gate: (me) => me.plex_enabled,
|
||||
Rail,
|
||||
Page: lazyWithPreloadAlongside(() => import("./PlexBrowse"), Rail),
|
||||
maxWidth: "100rem",
|
||||
};
|
||||
Reference in New Issue
Block a user