feat(player): the YouTube player and the file viewer are named in the address

The player names what it plays in its own history entry (watch, wrel, wtab): a reload or a
bookmark reopens it on the page that hosts it, with that page's list as the queue, or alone
from the catalog (GET /api/videos/{id}/row); a page that hosts none gets the shell's. Back
lands on the page's address without it (useBackToClose's url; ui/Modal passes it on). The
Files viewer and player do the same (fview, fplay). The Plex and channel-manager views are
always written in full, as their fallback is remembered state.
This commit is contained in:
2026-10-27 05:29:49 +01:00
parent d4f24de4ba
commit fa893c73a3
45 changed files with 1005 additions and 89 deletions
+6 -1
View File
@@ -65,6 +65,7 @@ export default function Modal({
placement = "center",
initialFocus,
headerActions,
url,
}: {
title: ReactNode;
initialFocus: ModalFocus;
@@ -81,10 +82,14 @@ export default function Modal({
placement?: "center" | "drawer" | "drawer-start" | "top";
/** Controls beside the ✕ in the header (a side panel's pin and customize, C2). */
headerActions?: ReactNode;
/** What the dialog names in the address while it is open, when it is part of the view (a viewer, a
* player): written into its own history entry, so a reload or a bookmark reopens it and Back lands
* on the address without it (lib/history). */
url?: Record<string, string | null>;
}) {
const { t } = useTranslation();
// Browser/mouse Back closes the topmost modal instead of navigating away.
useBackToClose(onClose);
useBackToClose(onClose, url);
// ONE container element for the dialog's whole life, re-parented between <body> and the fullscreen
// element — and attached before the children commit, so an `autoFocus` child
// still gets the focus. Both rules live in the hook; `ui/AnchoredPopover` shares it.