S4's reachability half. Home already led the nav order; what was missing was every way IN other than the rail. - Settings → Appearance → Startup: "Start on Home", the documented escape hatch for the landing override. Deliberately still localStorage and not a server pref — `loadInitialPage` answers before /api/me lands, and a landing module decided after the first paint is a visible jump. Its two stored values are now ONE exported constant, so the writer and the boot-time reader cannot drift on a magic string. - The command palette gets "Meglepetés" / "Surprise me": it navigates to Home and rerolls the hero on arrival. No second "Mit nézzek?" row beside it — the Home module row already matches that phrase through its descriptive title. - A global Shift+W jumps to Home. A chord, not a bare `w`: the Plex player binds bare letters and `w` sits on `s`, so a blind reach for the subtitle key would have thrown a viewer out of the film. The palette's request crosses a lazy module boundary, so it is a token that waits (lib/homeIntent) rather than an event fired into an empty room. Browser-measured: taken with a compare-and-clear, because StrictMode's double-invoked mount effect otherwise rerolled twice and landed on the page two picks deep. HU/EN parity kept; UAT on localdev in both languages, including the empty shelf.
54 lines
2.9 KiB
TypeScript
54 lines
2.9 KiB
TypeScript
// URL-derived navigation seeds, split out of NavigationProvider so they stay unit-testable AND the
|
|
// provider file keeps react-refresh happy (component-only exports). `loadInitialPage` picks the landing
|
|
// module from a bookmark's params; `channelViewFrom` is the shared _chan/?chan resolver behind BOTH the
|
|
// channelView seed and the onPop handler.
|
|
import { hasFilterParams, isPage, readPage, type Page } from "../lib/urlState";
|
|
import { getAccountRaw, LS } from "../lib/storage";
|
|
|
|
export const PAGE_KEY = LS.page;
|
|
// Module-local: the Settings toggle writes `LS.startOnHome` directly, so exporting this alias would
|
|
// only be a second name for the same key.
|
|
const START_HOME_KEY = LS.startOnHome;
|
|
|
|
/** The two stored values of the landing preference (raw strings, via useAccountPersistedState).
|
|
* Settings → Appearance → Startup writes them and `loadInitialPage` reads them, so they are defined
|
|
* ONCE: the same magic string spelled out in a writer and a reader is precisely the drift that has
|
|
* bitten this codebase before (`cancelled` vs `canceled`). */
|
|
export const START_ON_HOME = { on: "1", off: "0" } as const;
|
|
|
|
export interface ChannelView {
|
|
id: string;
|
|
name?: string;
|
|
}
|
|
|
|
export function loadInitialPage(): Page {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has("page")) return readPage();
|
|
// A view-specific param implies its page, so a bookmarked deep link lands on the right module even
|
|
// without an explicit ?page. Feed filters (or a channel overlay) imply the feed.
|
|
if (params.has("v")) return "plex";
|
|
if (params.has("pl")) return "playlists";
|
|
if (hasFilterParams(params) || params.has("chan")) return "feed";
|
|
// A bare URL → Home (E24), the surface whose whole job is to answer "what should I watch" before
|
|
// the user has to decide anything. It deliberately OVERRIDES the remembered last module: landing
|
|
// on whatever you happened to close the app on is the behaviour the anti-paralysis epic exists to
|
|
// replace. The account can opt out (Settings → Appearance → Startup) and get the remembered
|
|
// module back.
|
|
if (getAccountRaw(START_HOME_KEY) !== START_ON_HOME.off) return "home";
|
|
const stored = getAccountRaw(PAGE_KEY);
|
|
return isPage(stored) ? stored : "feed";
|
|
}
|
|
|
|
/** Resolve the open channel page from a landed history entry: the in-app `_chan` id (with its optional
|
|
* `_chanName`), else the ?chan URL param — a fresh ?chan bookmark's entry is URL-only. The seed AND
|
|
* the popstate handler share this ONE resolver so a popstate that lands on a bookmark entry (no
|
|
* `_chan`) restores the channel from the URL instead of wrongly closing it. */
|
|
export function channelViewFrom(
|
|
state: { _chan?: unknown; _chanName?: unknown } | null | undefined,
|
|
search: string,
|
|
): ChannelView | null {
|
|
const id =
|
|
(state?._chan as string | undefined) ?? new URLSearchParams(search).get("chan") ?? undefined;
|
|
return id ? { id, name: (state?._chanName as string) ?? undefined } : null;
|
|
}
|