// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { CodeEdit } from "@/app/view/codeedit"; import { PlotView } from "@/app/view/plotview"; import { PreviewView } from "@/app/view/preview"; import { TerminalView } from "@/app/view/term/term"; import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; import { ContextMenuModel } from "@/store/contextmenu"; import { atoms, setBlockFocus, useBlockAtom } from "@/store/global"; import * as WOS from "@/store/wos"; import clsx from "clsx"; import * as jotai from "jotai"; import * as React from "react"; import "./block.less"; const HoverPixels = 15; const HoverTimeoutMs = 100; interface BlockProps { blockId: string; onClose?: () => void; dragHandleRef?: React.RefObject; } function getBlockHeaderText(blockData: Block): string { if (!blockData) { return "no block data"; } return `${blockData?.view} [${blockData.oid.substring(0, 8)}]`; } interface FramelessBlockHeaderProps { blockId: string; onClose?: () => void; dragHandleRef?: React.RefObject; } const FramelessBlockHeader = ({ blockId, onClose, dragHandleRef }: FramelessBlockHeaderProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); return (
{getBlockHeaderText(blockData)}
{onClose && (
)}
); }; const hoverStateOff = "off"; const hoverStatePending = "pending"; const hoverStateOn = "on"; interface BlockFrameProps { blockId: string; onClose?: () => void; onClick?: () => void; preview: boolean; children?: React.ReactNode; blockRef?: React.RefObject; dragHandleRef?: React.RefObject; } const BlockFrame_Tech = ({ blockId, onClose, onClick, preview, blockRef, dragHandleRef, children, }: BlockFrameProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const isFocusedAtom = useBlockAtom(blockId, "isFocused", () => { return jotai.atom((get) => { const winData = get(atoms.waveWindow); return winData.activeblockid === blockId; }); }); let isFocused = jotai.useAtomValue(isFocusedAtom); if (preview) { isFocused = true; } function handleContextMenu(e: React.MouseEvent) { let menu: ContextMenuItem[] = []; menu.push({ label: "Close", click: onClose, }); ContextMenuModel.showContextMenu(menu, e); } return (
{getBlockHeaderText(blockData)}
{preview ?
: children}
); }; const BlockFrame_Frameless = ({ blockId, onClose, onClick, preview, blockRef, dragHandleRef, children, }: BlockFrameProps) => { const localBlockRef = React.useRef(null); const [showHeader, setShowHeader] = React.useState(preview ? true : false); const hoverState = React.useRef(hoverStateOff); // this forward the lcal ref to the blockRef React.useImperativeHandle(blockRef, () => localBlockRef.current); React.useEffect(() => { if (preview) { return; } const block = localBlockRef.current; let hoverTimeout: NodeJS.Timeout = null; const handleMouseMove = (event) => { const rect = block.getBoundingClientRect(); if (event.clientY - rect.top <= HoverPixels) { if (hoverState.current == hoverStateOff) { hoverTimeout = setTimeout(() => { if (hoverState.current == hoverStatePending) { hoverState.current = hoverStateOn; setShowHeader(true); } }, HoverTimeoutMs); hoverState.current = hoverStatePending; } } else { if (hoverTimeout) { if (hoverState.current == hoverStatePending) { hoverState.current = hoverStateOff; } clearTimeout(hoverTimeout); hoverTimeout = null; } } }; block.addEventListener("mousemove", handleMouseMove); return () => { block.removeEventListener("mousemove", handleMouseMove); }; }); let mouseLeaveHandler = () => { if (preview) { return; } setShowHeader(false); hoverState.current = hoverStateOff; }; return (
{preview ?
: children}
); }; const BlockFrame = (props: BlockFrameProps) => { const blockId = props.blockId; const [blockData, blockDataLoading] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); const tabData = jotai.useAtomValue(atoms.tabAtom); if (!blockId || !blockData) { return null; } // if 0 or 1 blocks, use frameless, otherwise use tech const numBlocks = tabData?.blockids?.length ?? 0; if (numBlocks <= 1) { return ; } return ; }; const Block = ({ blockId, onClose, dragHandleRef }: BlockProps) => { let blockElem: JSX.Element = null; const focusElemRef = React.useRef(null); const blockRef = React.useRef(null); const [blockClicked, setBlockClicked] = React.useState(false); const [blockData, blockDataLoading] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId)); React.useLayoutEffect(() => { if (!blockClicked) { return; } setBlockClicked(false); const focusWithin = blockRef.current?.contains(document.activeElement); if (!focusWithin) { focusElemRef.current?.focus(); } setBlockFocus(blockId); }, [blockClicked]); if (!blockId || !blockData) return null; if (blockDataLoading) { blockElem = Loading...; } else if (blockData.view === "term") { blockElem = ; } else if (blockData.view === "preview") { blockElem = ; } else if (blockData.view === "plot") { blockElem = ; } else if (blockData.view === "codeedit") { blockElem = ; } return ( setBlockClicked(true)} blockRef={blockRef} dragHandleRef={dragHandleRef} >
{}} />
Loading...}>{blockElem}
); }; export { Block, BlockFrame };