feat(nav): each module declares its URL keys; a page switch clears them and a page opens without another's
The shell cleared a hand-kept key list on a page switch (it missed the Files and feed keys) and cleared nothing when a page opened from a link carrying another page's keys. ModuleDef.urlKeys now names every key a page's view writes, the shell derives both from the registry, and moduleUrl.contract.test holds the declarations true: one owner per key, no shell key, exactly what each codec writes, and no undeclared key literal in the app.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { ScrollText } from "lucide-react";
|
||||
import { lazyWithPreload } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
import { ACTIVITY_KEYS } from "../../lib/activityUrlState";
|
||||
|
||||
// This page as the shell and the navigation know it (lib/moduleDef). Its page loads lazily.
|
||||
export const activityModule: ModuleDef = {
|
||||
@@ -12,4 +13,5 @@ export const activityModule: ModuleDef = {
|
||||
system: true,
|
||||
gate: (me) => me.role === "admin",
|
||||
Page: lazyWithPreload(() => import("./Activity")),
|
||||
urlKeys: ACTIVITY_KEYS,
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Tv } from "lucide-react";
|
||||
import { lazyWithPreload } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
import { CHANNELS_KEYS } from "../../lib/channelsUrlState";
|
||||
import { ChannelsProvider } from "./ChannelsProvider";
|
||||
|
||||
// This page as the shell and the navigation know it (lib/moduleDef). Its page loads lazily.
|
||||
@@ -13,4 +14,5 @@ export const channelsModule: ModuleDef = {
|
||||
Page: lazyWithPreload(() => import("./Channels")),
|
||||
maxWidth: "96rem",
|
||||
Store: ChannelsProvider,
|
||||
urlKeys: CHANNELS_KEYS,
|
||||
};
|
||||
|
||||
@@ -12,4 +12,5 @@ export const configModule: ModuleDef = {
|
||||
system: true,
|
||||
gate: (me) => me.role === "admin",
|
||||
Page: lazyWithPreload(() => import("./ConfigPanel")),
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { YouTubeIcon } from "../../lib/brandIcons";
|
||||
import { lazyWithPreload, lazyWithPreloadAlongside } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
import { FEED_KEYS } from "../../lib/urlState";
|
||||
import { FeedStore } from "./FeedStore";
|
||||
|
||||
// The rail loads with the page (lazyWithPreloadAlongside), so it never arrives after it.
|
||||
@@ -15,6 +16,7 @@ export const feedModule: ModuleDef = {
|
||||
Rail,
|
||||
Page: lazyWithPreloadAlongside(() => import("./FeedPage"), Rail),
|
||||
Store: FeedStore,
|
||||
urlKeys: FEED_KEYS,
|
||||
};
|
||||
|
||||
// The channel page: a feed scoped to one channel, opened over any page (App renders it in place of the
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
||||
import { LS, getAccountRaw, setAccountRaw } from "../../lib/storage";
|
||||
import { useUrlMirror } from "../../lib/useUrlMirror";
|
||||
import { FILES_FOLDER_KEY, FILES_ITEM_KEY } from "../../lib/filesUrlState";
|
||||
import { useNavigation, useNavigationActions } from "../../features/navigation/navigationContext";
|
||||
import {
|
||||
ALL_FILES,
|
||||
@@ -33,14 +34,14 @@ import {
|
||||
|
||||
function initialView(): FilesView {
|
||||
return (
|
||||
parseView(new URLSearchParams(window.location.search).get("folder")) ??
|
||||
parseView(new URLSearchParams(window.location.search).get(FILES_FOLDER_KEY)) ??
|
||||
parseView(getAccountRaw(LS.filesView)) ??
|
||||
ALL_FILES
|
||||
);
|
||||
}
|
||||
|
||||
function initialFocus(): FilesFocus | null {
|
||||
const id = parseItem(new URLSearchParams(window.location.search).get("item"));
|
||||
const id = parseItem(new URLSearchParams(window.location.search).get(FILES_ITEM_KEY));
|
||||
return id === null ? null : { id, token: 1 };
|
||||
}
|
||||
|
||||
@@ -87,8 +88,8 @@ export function FilesProvider({ children }: { children: ReactNode }) {
|
||||
const arrived = page === "files" ? arrivedRaw : null;
|
||||
|
||||
useUrlMirror({
|
||||
folder: page === "files" ? viewParam(view) : null,
|
||||
item: focus ? String(focus.id) : null,
|
||||
[FILES_FOLDER_KEY]: page === "files" ? viewParam(view) : null,
|
||||
[FILES_ITEM_KEY]: focus ? String(focus.id) : null,
|
||||
});
|
||||
|
||||
const state = useMemo<FilesState>(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FolderOpen } from "lucide-react";
|
||||
import { lazyWithPreload, lazyWithPreloadAlongside } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
import { FILES_KEYS } from "../../lib/filesUrlState";
|
||||
import { FilesProvider } from "./FilesProvider";
|
||||
|
||||
// The rail loads with the page (lazyWithPreloadAlongside), so it never arrives after it.
|
||||
@@ -16,4 +17,5 @@ export const filesModule: ModuleDef = {
|
||||
Page: lazyWithPreloadAlongside(() => import("./Files"), Rail),
|
||||
maxWidth: "80rem",
|
||||
Store: FilesProvider,
|
||||
urlKeys: FILES_KEYS,
|
||||
};
|
||||
|
||||
@@ -11,4 +11,7 @@ export const homeModule: ModuleDef = {
|
||||
icon: Home,
|
||||
Page: lazyWithPreload(() => import("./Home")),
|
||||
maxWidth: "80rem",
|
||||
// Nothing of its own: the question is an answer, not a setting — the same question draws other
|
||||
// cards once what you have watched moves on.
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -11,4 +11,5 @@ export const notificationsModule: ModuleDef = {
|
||||
hasBadge: true,
|
||||
Page: lazyWithPreload(() => import("./NotificationsPanel")),
|
||||
maxWidth: "48rem",
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -16,4 +16,6 @@ export const playlistsModule: ModuleDef = {
|
||||
Page: lazyWithPreloadAlongside(() => import("./Playlists"), Rail),
|
||||
maxWidth: "64rem",
|
||||
Store: PlaylistsProvider,
|
||||
// The playlist shown (PlaylistsProvider, playlistsInit).
|
||||
urlKeys: ["pl"],
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Clapperboard } from "lucide-react";
|
||||
import { lazyWithPreload, lazyWithPreloadAlongside } from "../../lib/lazyWithPreload";
|
||||
import type { ModuleDef } from "../../lib/moduleDef";
|
||||
import { PLEX_KEYS, PLEX_SUB_KEY } from "../../lib/plexUrlState";
|
||||
import { PlexStore } from "./PlexStore";
|
||||
|
||||
// The rail loads with the page (lazyWithPreloadAlongside), so it never arrives after it.
|
||||
@@ -17,4 +18,5 @@ export const plexModule: ModuleDef = {
|
||||
Page: lazyWithPreloadAlongside(() => import("./PlexBrowse"), Rail),
|
||||
maxWidth: "100rem",
|
||||
Store: PlexStore,
|
||||
urlKeys: [...PLEX_KEYS, PLEX_SUB_KEY],
|
||||
};
|
||||
|
||||
@@ -12,4 +12,5 @@ export const schedulerModule: ModuleDef = {
|
||||
system: true,
|
||||
gate: (me) => me.role === "admin",
|
||||
Page: lazyWithPreload(() => import("./Scheduler")),
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -10,4 +10,5 @@ export const settingsModule: ModuleDef = {
|
||||
titleKey: "settings.title",
|
||||
icon: Settings,
|
||||
Page: lazyWithPreload(() => import("./SettingsPanel")),
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -10,4 +10,5 @@ export const statsModule: ModuleDef = {
|
||||
titleKey: "header.usageStats",
|
||||
icon: BarChart3,
|
||||
Page: lazyWithPreload(() => import("./Stats")),
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
@@ -13,4 +13,5 @@ export const usersModule: ModuleDef = {
|
||||
gate: (me) => me.role === "admin",
|
||||
Page: lazyWithPreload(() => import("./AdminUsers")),
|
||||
maxWidth: "48rem",
|
||||
urlKeys: [],
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user