134 lines · 4.4 KB · TypeScript
RawDownload
"use client"; import { Minus, MoreVertical, Plus as PlusIcon, RotateCcw } from "lucide-react"; import { Button } from "~/components/ui/button";import { Menu, MenuItem, MenuPopup, MenuSeparator, MenuTrigger } from "~/components/ui/menu";import { Tooltip, TooltipPopup, TooltipTrigger } from "~/components/ui/tooltip"; import { previewBridge } from "./previewBridge";import { reportPreviewActionFailure } from "./reportPreviewActionFailure"; interface Props {  /** Active preview tab id. Tab-targeting actions are disabled without it. */  tabId: string | null;  /**   * True only after the desktop bridge has registered a `webContentsId` for   * the active tab. Tab-targeting actions throw on the desktop side until   * then; we disable those items so the menu doesn't fire silent no-ops.   */  hasWebContents: boolean;  /** Current zoom factor as a number (1.0 = 100%). */  zoomFactor: number;} /** * Three-dot menu in the chrome row. Wires Hard reload, DevTools, zoom * controls, and storage-clearing actions. Only mounted by `PreviewView` * when the desktop bridge is present, so we can call it unconditionally. */export function PreviewMoreMenu({ tabId, hasWebContents, zoomFactor }: Props) {  if (!previewBridge) return null;  const bridge = previewBridge;  const tabDisabled = !tabId || !hasWebContents;  const callTab = (operation: string, op: (tabId: string) => Promise<void>) => () => {    if (!tabId) return;    void op(tabId).catch((cause) => {      reportPreviewActionFailure({ operation, tabId }, cause);    });  };   const zoomLabel = `${Math.round(zoomFactor * 100)}%`;  return (    <Menu>      <Tooltip>        <TooltipTrigger          render={            <MenuTrigger              render={                <Button variant="ghost" size="icon-xs" type="button" aria-label="Preview menu" />              }            />          }        >          <MoreVertical />        </TooltipTrigger>        <TooltipPopup>More</TooltipPopup>      </Tooltip>      <MenuPopup align="end" sideOffset={6} className="min-w-56">        <MenuItem onClick={callTab("hard-reload", bridge.hardReload)} disabled={tabDisabled}>          Hard reload        </MenuItem>        <MenuItem onClick={callTab("open-devtools", bridge.openDevTools)} disabled={tabDisabled}>          Open DevTools        </MenuItem>        {/*          Zoom row: label + inline control cluster. `closeOnClick=false`          keeps the menu open while the user clicks the +/− buttons.        */}        <MenuItem          closeOnClick={false}          onClick={(event: React.MouseEvent) => event.preventDefault()}          className="justify-between"          disabled={tabDisabled}        >          <span>Zoom</span>          <span className="flex items-center gap-1">            <Button              variant="outline"              size="icon-xs"              type="button"              onClick={callTab("zoom-out", bridge.zoomOut)}              aria-label="Zoom out"              disabled={tabDisabled}            >              <Minus />            </Button>            <span className="min-w-12 text-center text-xs tabular-nums text-muted-foreground">              {zoomLabel}            </span>            <Button              variant="outline"              size="icon-xs"              type="button"              onClick={callTab("zoom-in", bridge.zoomIn)}              aria-label="Zoom in"              disabled={tabDisabled}            >              <PlusIcon />            </Button>            <Button              variant="ghost"              size="icon-xs"              type="button"              onClick={callTab("reset-zoom", bridge.resetZoom)}              aria-label="Reset zoom"              disabled={tabDisabled}            >              <RotateCcw />            </Button>          </span>        </MenuItem>        <MenuSeparator />        <MenuItem          onClick={() =>            void bridge.clearCookies().catch((cause) => {              reportPreviewActionFailure({ operation: "clear-cookies" }, cause);            })          }        >          Clear cookies        </MenuItem>        <MenuItem          onClick={() =>            void bridge.clearCache().catch((cause) => {              reportPreviewActionFailure({ operation: "clear-cache" }, cause);            })          }        >          Clear cache        </MenuItem>      </MenuPopup>    </Menu>  );}