A right-click on the EMPTY area of a page opens a menu of that page's own toolbar: the feed (sort, reshuffle, view, watch state, content, source, clear filters), the Plex library grid (sort, watch state, clear), the channel manager (layout, the cards' sort, sync status, edit tags, clear the tag filter, read subscriptions), a playlist (play all, group by, reset view, save order, undo/redo, the list's own menu) and the notification center (mark all read). No destructive row and no backfill. PageShell listens on the page area; lib/backgroundTarget decides empty (not text, a control, a link, media, or a row/card with its own menu). Shift+right- click, a touch long-press and a right-click on selected words keep the browser's menu. ui/PageMenu registers while mounted, so a loading or error state keeps the browser's menu too. The rows mirror each control through ui/pageMenuItems (sortSubmenu via pickSort, choiceSubmenu, multiSubmenu). pageMenuScope.contract.test.ts pins the five pages.
34 lines
1.8 KiB
TypeScript
34 lines
1.8 KiB
TypeScript
import { createContext, useContext, useEffect } from "react";
|
|
import { useContextMenu, type ContextMenuController } from "./useContextMenu";
|
|
|
|
// docs/DESIGN.md "Background menu": a right-click on the EMPTY area of a page opens a menu of that
|
|
// page's own toolbar actions (sort, view, quick filters) — never a new function, and desktop only.
|
|
// PageShell listens on the page area and decides "empty" (lib/backgroundTarget); a page that has such a
|
|
// menu renders `<PageMenu>` (./PageMenu), which registers here while mounted and draws the same
|
|
// `ContextMenu` as a row's, from the page's live state. A page that registers nothing keeps the
|
|
// browser's menu everywhere.
|
|
//
|
|
// Its own module (not in PageShell.tsx / PageMenu.tsx) so those export only components (Fast Refresh).
|
|
|
|
/** Opens the page's menu at a viewport point, anchored to the page area. */
|
|
export type PageMenuOpener = (x: number, y: number, contextElement: Element) => void;
|
|
|
|
/** PageShell's registry: the latest registered opener answers (a feed inside a channel page takes
|
|
* over while mounted); the returned function unregisters. */
|
|
export const PageMenuSlot = createContext<((opener: PageMenuOpener) => () => void) | null>(null);
|
|
|
|
/** The page's background menu: render `<ContextMenu menu={this} …/>` with the page's actions, in
|
|
* every state where `enabled` — a menu registered with no surface drawn would eat the right-click
|
|
* and show nothing. `<PageMenu>` is the short form; a page that needs the open state (a row that
|
|
* fetches on open) takes the hook. */
|
|
export function usePageMenu(enabled = true): ContextMenuController<"page"> {
|
|
const register = useContext(PageMenuSlot);
|
|
const menu = useContextMenu<"page">();
|
|
const { openAt } = menu;
|
|
useEffect(
|
|
() => (enabled ? register?.((x, y, el) => openAt(x, y, "page", el)) : undefined),
|
|
[enabled, register, openAt],
|
|
);
|
|
return menu;
|
|
}
|