// Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 import { BlockComponentModel, BlockProps } from "@/app/block/blocktypes"; import { PlotView } from "@/app/view/plotview/plotview"; import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview"; import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; import { NodeModel, useDebouncedNodeInnerRect } from "@/layout/index"; import { counterInc, getViewModel, registerViewModel, unregisterViewModel } from "@/store/global"; import * as WOS from "@/store/wos"; import * as util from "@/util/util"; import { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel } from "@/view/cpuplot/cpuplot"; import { HelpView } from "@/view/helpview/helpview"; import { TermViewModel, TerminalView, makeTerminalModel } from "@/view/term/term"; import { WaveAi, WaveAiModel, makeWaveAiViewModel } from "@/view/waveai/waveai"; import { WebView, WebViewModel, makeWebViewModel } from "@/view/webview/webview"; import * as jotai from "jotai"; import * as React from "react"; import "./block.less"; import { BlockFrame } from "./blockframe"; import { blockViewToIcon, blockViewToName } from "./blockutil"; type FullBlockProps = { preview: boolean; nodeModel: NodeModel; viewModel: ViewModel; }; function makeViewModel(blockId: string, blockView: string, nodeModel: NodeModel): ViewModel { if (blockView === "term") { return makeTerminalModel(blockId); } if (blockView === "preview") { return makePreviewModel(blockId); } if (blockView === "web") { return makeWebViewModel(blockId, nodeModel); } if (blockView === "waveai") { return makeWaveAiViewModel(blockId); } if (blockView === "cpuplot") { return makeCpuPlotViewModel(blockId); } return makeDefaultViewModel(blockId, blockView); } function getViewElem( blockId: string, blockRef: React.RefObject, contentRef: React.RefObject, blockView: string, viewModel: ViewModel ): JSX.Element { if (util.isBlank(blockView)) { return No View; } if (blockView === "term") { return ; } if (blockView === "preview") { return ( ); } if (blockView === "plot") { return ; } if (blockView === "web") { return ; } if (blockView === "waveai") { return ; } if (blockView === "cpuplot") { return ; } if (blockView == "help") { return ; } return Invalid View "{blockView}"; } function makeDefaultViewModel(blockId: string, viewType: string): ViewModel { const blockDataAtom = WOS.getWaveObjectAtom(WOS.makeORef("block", blockId)); let viewModel: ViewModel = { viewType: viewType, viewIcon: jotai.atom((get) => { const blockData = get(blockDataAtom); return blockViewToIcon(blockData?.meta?.view); }), viewName: jotai.atom((get) => { const blockData = get(blockDataAtom); return blockViewToName(blockData?.meta?.view); }), viewText: jotai.atom((get) => { const blockData = get(blockDataAtom); return blockData?.meta?.title; }), preIconButton: jotai.atom(null), endIconButtons: jotai.atom(null), }; return viewModel; } const BlockPreview = React.memo(({ nodeModel, viewModel }: FullBlockProps) => { const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", nodeModel.blockId)); if (!blockData) { return null; } return ( ); }); const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => { counterInc("render-BlockFull"); const focusElemRef = React.useRef(null); const blockRef = React.useRef(null); const contentRef = React.useRef(null); const [blockClicked, setBlockClicked] = React.useState(false); const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", nodeModel.blockId)); const [focusedChild, setFocusedChild] = React.useState(null); const isFocused = jotai.useAtomValue(nodeModel.isFocused); const disablePointerEvents = jotai.useAtomValue(nodeModel.disablePointerEvents); const innerRect = useDebouncedNodeInnerRect(nodeModel); React.useLayoutEffect(() => { setBlockClicked(isFocused); }, [isFocused]); React.useLayoutEffect(() => { if (!blockClicked) { return; } setBlockClicked(false); const focusWithin = blockRef.current?.contains(document.activeElement); if (!focusWithin) { setFocusTarget(); } nodeModel.focusNode(); }, [blockClicked]); React.useLayoutEffect(() => { if (focusedChild == null) { return; } nodeModel.focusNode(); }, [focusedChild]); // treat the block as clicked on creation const setBlockClickedTrue = React.useCallback(() => { setBlockClicked(true); }, []); const [blockContentOffset, setBlockContentOffset] = React.useState(); React.useEffect(() => { if (blockRef.current && contentRef.current) { const blockRect = blockRef.current.getBoundingClientRect(); const contentRect = contentRef.current.getBoundingClientRect(); setBlockContentOffset({ top: 0, left: 0, width: blockRect.width - contentRect.width, height: blockRect.height - contentRect.height, }); } }, [blockRef, contentRef]); const blockContentStyle = React.useMemo(() => { const retVal: React.CSSProperties = { pointerEvents: disablePointerEvents ? "none" : undefined, }; if (innerRect?.width && innerRect.height && blockContentOffset) { retVal.width = `calc(${innerRect?.width} - ${blockContentOffset.width}px)`; retVal.height = `calc(${innerRect?.height} - ${blockContentOffset.height}px)`; } return retVal; }, [innerRect, disablePointerEvents, blockContentOffset]); const viewElem = React.useMemo( () => getViewElem(nodeModel.blockId, blockRef, contentRef, blockData?.meta?.view, viewModel), [nodeModel.blockId, blockData?.meta?.view, viewModel] ); const determineFocusedChild = React.useCallback( (event: React.FocusEvent) => { setFocusedChild(event.target); }, [setFocusedChild] ); const setFocusTarget = React.useCallback(() => { const ok = viewModel?.giveFocus?.(); if (ok) { return; } focusElemRef.current?.focus({ preventScroll: true }); }, []); const blockModel: BlockComponentModel = { onClick: setBlockClickedTrue, onFocusCapture: determineFocusedChild, blockRef: blockRef, }; return (
{}} />
Loading...}>{viewElem}
); }); const Block = React.memo((props: BlockProps) => { counterInc("render-Block"); counterInc("render-Block-" + props.nodeModel.blockId.substring(0, 8)); const [blockData, loading] = WOS.useWaveObjectValue(WOS.makeORef("block", props.nodeModel.blockId)); let viewModel = getViewModel(props.nodeModel.blockId); if (viewModel == null || viewModel.viewType != blockData?.meta?.view) { viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel); registerViewModel(props.nodeModel.blockId, viewModel); } React.useEffect(() => { return () => { unregisterViewModel(props.nodeModel.blockId); }; }, []); if (loading || util.isBlank(props.nodeModel.blockId) || blockData == null) { return null; } if (props.preview) { return ; } return ; }); export { Block };