2024-05-13 23:40:18 +02:00
|
|
|
// Copyright 2024, Command Line Inc.
|
|
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
import { BlockComponentModel, BlockProps } from "@/app/block/blocktypes";
|
2024-08-01 22:06:18 +02:00
|
|
|
import { PlotView } from "@/app/view/plotview/plotview";
|
2024-08-22 00:49:23 +02:00
|
|
|
import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview";
|
2024-06-03 20:35:06 +02:00
|
|
|
import { ErrorBoundary } from "@/element/errorboundary";
|
2024-05-17 07:48:23 +02:00
|
|
|
import { CenteredDiv } from "@/element/quickelems";
|
2024-08-26 20:56:00 +02:00
|
|
|
import { NodeModel } from "@/layout/index";
|
|
|
|
import { counterInc, getViewModel, registerViewModel, unregisterViewModel } from "@/store/global";
|
2024-05-28 21:12:28 +02:00
|
|
|
import * as WOS from "@/store/wos";
|
2024-06-21 23:44:11 +02:00
|
|
|
import * as util from "@/util/util";
|
2024-08-22 00:49:23 +02:00
|
|
|
import { CpuPlotView, CpuPlotViewModel, makeCpuPlotViewModel } from "@/view/cpuplot/cpuplot";
|
2024-08-01 22:06:18 +02:00
|
|
|
import { HelpView } from "@/view/helpview/helpview";
|
2024-08-22 00:49:23 +02:00
|
|
|
import { TermViewModel, TerminalView, makeTerminalModel } from "@/view/term/term";
|
|
|
|
import { WaveAi, WaveAiModel, makeWaveAiViewModel } from "@/view/waveai/waveai";
|
|
|
|
import { WebView, WebViewModel, makeWebViewModel } from "@/view/webview/webview";
|
2024-06-19 08:44:53 +02:00
|
|
|
import * as jotai from "jotai";
|
2024-05-28 21:12:28 +02:00
|
|
|
import * as React from "react";
|
2024-08-26 20:56:00 +02:00
|
|
|
import "./block.less";
|
2024-08-02 00:35:13 +02:00
|
|
|
import { BlockFrame } from "./blockframe";
|
|
|
|
import { blockViewToIcon, blockViewToName } from "./blockutil";
|
2024-05-13 23:40:18 +02:00
|
|
|
|
2024-08-22 00:49:23 +02:00
|
|
|
type FullBlockProps = {
|
|
|
|
preview: boolean;
|
2024-08-26 20:56:00 +02:00
|
|
|
nodeModel: NodeModel;
|
2024-08-22 00:49:23 +02:00
|
|
|
viewModel: ViewModel;
|
|
|
|
};
|
|
|
|
|
|
|
|
function makeViewModel(blockId: string, blockView: string): ViewModel {
|
|
|
|
if (blockView === "term") {
|
|
|
|
return makeTerminalModel(blockId);
|
|
|
|
}
|
|
|
|
if (blockView === "preview") {
|
|
|
|
return makePreviewModel(blockId);
|
|
|
|
}
|
|
|
|
if (blockView === "web") {
|
|
|
|
return makeWebViewModel(blockId);
|
|
|
|
}
|
|
|
|
if (blockView === "waveai") {
|
|
|
|
return makeWaveAiViewModel(blockId);
|
|
|
|
}
|
|
|
|
if (blockView === "cpuplot") {
|
|
|
|
return makeCpuPlotViewModel(blockId);
|
|
|
|
}
|
2024-08-23 01:25:53 +02:00
|
|
|
return makeDefaultViewModel(blockId, blockView);
|
2024-08-22 00:49:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function getViewElem(blockId: string, blockView: string, viewModel: ViewModel): JSX.Element {
|
2024-07-30 21:33:28 +02:00
|
|
|
if (util.isBlank(blockView)) {
|
2024-08-22 00:49:23 +02:00
|
|
|
return <CenteredDiv>No View</CenteredDiv>;
|
|
|
|
}
|
|
|
|
if (blockView === "term") {
|
|
|
|
return <TerminalView key={blockId} blockId={blockId} model={viewModel as TermViewModel} />;
|
|
|
|
}
|
|
|
|
if (blockView === "preview") {
|
|
|
|
return <PreviewView key={blockId} blockId={blockId} model={viewModel as PreviewModel} />;
|
|
|
|
}
|
|
|
|
if (blockView === "plot") {
|
|
|
|
return <PlotView key={blockId} />;
|
|
|
|
}
|
|
|
|
if (blockView === "web") {
|
|
|
|
return <WebView key={blockId} blockId={blockId} model={viewModel as WebViewModel} />;
|
|
|
|
}
|
|
|
|
if (blockView === "waveai") {
|
|
|
|
return <WaveAi key={blockId} blockId={blockId} model={viewModel as WaveAiModel} />;
|
|
|
|
}
|
|
|
|
if (blockView === "cpuplot") {
|
|
|
|
return <CpuPlotView key={blockId} blockId={blockId} model={viewModel as CpuPlotViewModel} />;
|
|
|
|
}
|
|
|
|
if (blockView == "help") {
|
|
|
|
return <HelpView key={blockId} blockId={blockId} />;
|
|
|
|
}
|
|
|
|
return <CenteredDiv>Invalid View "{blockView}"</CenteredDiv>;
|
2024-06-26 18:31:43 +02:00
|
|
|
}
|
|
|
|
|
2024-08-23 01:25:53 +02:00
|
|
|
function makeDefaultViewModel(blockId: string, viewType: string): ViewModel {
|
2024-07-09 00:04:48 +02:00
|
|
|
const blockDataAtom = WOS.getWaveObjectAtom<Block>(WOS.makeORef("block", blockId));
|
|
|
|
let viewModel: ViewModel = {
|
2024-08-23 01:25:53 +02:00
|
|
|
viewType: viewType,
|
2024-07-09 00:04:48 +02:00
|
|
|
viewIcon: jotai.atom((get) => {
|
|
|
|
const blockData = get(blockDataAtom);
|
2024-07-30 21:33:28 +02:00
|
|
|
return blockViewToIcon(blockData?.meta?.view);
|
2024-07-09 00:04:48 +02:00
|
|
|
}),
|
|
|
|
viewName: jotai.atom((get) => {
|
|
|
|
const blockData = get(blockDataAtom);
|
2024-07-30 21:33:28 +02:00
|
|
|
return blockViewToName(blockData?.meta?.view);
|
2024-07-09 00:04:48 +02:00
|
|
|
}),
|
|
|
|
viewText: jotai.atom((get) => {
|
|
|
|
const blockData = get(blockDataAtom);
|
|
|
|
return blockData?.meta?.title;
|
|
|
|
}),
|
2024-07-09 01:36:30 +02:00
|
|
|
preIconButton: jotai.atom(null),
|
|
|
|
endIconButtons: jotai.atom(null),
|
2024-07-09 00:04:48 +02:00
|
|
|
};
|
|
|
|
return viewModel;
|
|
|
|
}
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
const BlockPreview = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
|
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
2024-07-09 00:04:48 +02:00
|
|
|
if (!blockData) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<BlockFrame
|
2024-08-26 20:56:00 +02:00
|
|
|
key={nodeModel.blockId}
|
|
|
|
nodeModel={nodeModel}
|
2024-07-09 00:04:48 +02:00
|
|
|
preview={true}
|
|
|
|
blockModel={null}
|
|
|
|
viewModel={viewModel}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
const BlockFull = React.memo(({ nodeModel, viewModel }: FullBlockProps) => {
|
2024-08-22 00:49:23 +02:00
|
|
|
counterInc("render-BlockFull");
|
2024-06-19 20:58:22 +02:00
|
|
|
const focusElemRef = React.useRef<HTMLInputElement>(null);
|
|
|
|
const blockRef = React.useRef<HTMLDivElement>(null);
|
|
|
|
const [blockClicked, setBlockClicked] = React.useState(false);
|
2024-08-26 20:56:00 +02:00
|
|
|
const [blockData] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", nodeModel.blockId));
|
2024-07-03 23:34:55 +02:00
|
|
|
const [focusedChild, setFocusedChild] = React.useState(null);
|
2024-08-26 20:56:00 +02:00
|
|
|
const isFocused = jotai.useAtomValue(nodeModel.isFocused);
|
|
|
|
const disablePointerEvents = jotai.useAtomValue(nodeModel.disablePointerEvents);
|
|
|
|
const addlProps = jotai.useAtomValue(nodeModel.additionalProps);
|
2024-07-03 23:34:55 +02:00
|
|
|
|
|
|
|
React.useLayoutEffect(() => {
|
|
|
|
setBlockClicked(isFocused);
|
|
|
|
}, [isFocused]);
|
2024-06-19 20:58:22 +02:00
|
|
|
|
|
|
|
React.useLayoutEffect(() => {
|
|
|
|
if (!blockClicked) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
setBlockClicked(false);
|
|
|
|
const focusWithin = blockRef.current?.contains(document.activeElement);
|
|
|
|
if (!focusWithin) {
|
2024-07-03 23:34:55 +02:00
|
|
|
setFocusTarget();
|
2024-06-19 20:58:22 +02:00
|
|
|
}
|
2024-08-26 20:56:00 +02:00
|
|
|
nodeModel.focusNode();
|
2024-06-19 20:58:22 +02:00
|
|
|
}, [blockClicked]);
|
|
|
|
|
2024-07-03 23:34:55 +02:00
|
|
|
React.useLayoutEffect(() => {
|
|
|
|
if (focusedChild == null) {
|
|
|
|
return;
|
|
|
|
}
|
2024-08-26 20:56:00 +02:00
|
|
|
nodeModel.focusNode();
|
|
|
|
}, [focusedChild]);
|
2024-07-03 23:34:55 +02:00
|
|
|
|
|
|
|
// treat the block as clicked on creation
|
2024-06-26 18:31:43 +02:00
|
|
|
const setBlockClickedTrue = React.useCallback(() => {
|
|
|
|
setBlockClicked(true);
|
|
|
|
}, []);
|
|
|
|
|
2024-08-26 20:56:00 +02:00
|
|
|
const viewElem = React.useMemo(
|
|
|
|
() => getViewElem(nodeModel.blockId, blockData?.meta?.view, viewModel),
|
|
|
|
[nodeModel.blockId, blockData?.meta?.view, viewModel]
|
2024-07-23 01:41:18 +02:00
|
|
|
);
|
|
|
|
|
2024-07-03 23:34:55 +02:00
|
|
|
const determineFocusedChild = React.useCallback(
|
|
|
|
(event: React.FocusEvent<HTMLDivElement, Element>) => {
|
|
|
|
setFocusedChild(event.target);
|
|
|
|
},
|
|
|
|
[setFocusedChild]
|
|
|
|
);
|
|
|
|
|
|
|
|
const setFocusTarget = React.useCallback(() => {
|
2024-07-23 01:41:18 +02:00
|
|
|
const ok = viewModel?.giveFocus?.();
|
|
|
|
if (ok) {
|
|
|
|
return;
|
2024-07-03 23:34:55 +02:00
|
|
|
}
|
2024-07-31 07:53:50 +02:00
|
|
|
focusElemRef.current?.focus({ preventScroll: true });
|
2024-07-23 01:41:18 +02:00
|
|
|
}, []);
|
2024-07-09 00:04:48 +02:00
|
|
|
|
|
|
|
const blockModel: BlockComponentModel = {
|
|
|
|
onClick: setBlockClickedTrue,
|
|
|
|
onFocusCapture: determineFocusedChild,
|
|
|
|
blockRef: blockRef,
|
|
|
|
};
|
2024-07-03 23:34:55 +02:00
|
|
|
|
2024-05-13 23:40:18 +02:00
|
|
|
return (
|
2024-06-19 20:58:22 +02:00
|
|
|
<BlockFrame
|
2024-08-26 20:56:00 +02:00
|
|
|
key={nodeModel.blockId}
|
|
|
|
nodeModel={nodeModel}
|
2024-06-19 20:58:22 +02:00
|
|
|
preview={false}
|
2024-07-09 00:04:48 +02:00
|
|
|
blockModel={blockModel}
|
|
|
|
viewModel={viewModel}
|
2024-06-19 20:58:22 +02:00
|
|
|
>
|
|
|
|
<div key="focuselem" className="block-focuselem">
|
2024-08-26 20:56:00 +02:00
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
value=""
|
|
|
|
ref={focusElemRef}
|
|
|
|
id={`${nodeModel.blockId}-dummy-focus`}
|
|
|
|
onChange={() => {}}
|
|
|
|
/>
|
2024-06-19 20:58:22 +02:00
|
|
|
</div>
|
2024-07-31 21:49:38 +02:00
|
|
|
<div
|
|
|
|
key="content"
|
|
|
|
className="block-content"
|
2024-08-26 20:56:00 +02:00
|
|
|
style={{
|
|
|
|
pointerEvents: disablePointerEvents ? "none" : undefined,
|
|
|
|
width: addlProps?.transform?.width,
|
|
|
|
height: addlProps?.transform?.height,
|
|
|
|
}}
|
2024-07-31 21:49:38 +02:00
|
|
|
>
|
2024-06-03 20:35:06 +02:00
|
|
|
<ErrorBoundary>
|
2024-07-09 00:04:48 +02:00
|
|
|
<React.Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{viewElem}</React.Suspense>
|
2024-06-03 20:35:06 +02:00
|
|
|
</ErrorBoundary>
|
2024-05-14 08:45:41 +02:00
|
|
|
</div>
|
2024-06-19 08:44:53 +02:00
|
|
|
</BlockFrame>
|
2024-05-13 23:40:18 +02:00
|
|
|
);
|
2024-06-26 18:31:43 +02:00
|
|
|
});
|
2024-05-13 23:40:18 +02:00
|
|
|
|
2024-07-09 00:04:48 +02:00
|
|
|
const Block = React.memo((props: BlockProps) => {
|
2024-08-22 00:49:23 +02:00
|
|
|
counterInc("render-Block");
|
2024-08-26 20:56:00 +02:00
|
|
|
counterInc("render-Block-" + props.nodeModel.blockId.substring(0, 8));
|
|
|
|
const [blockData, loading] = WOS.useWaveObjectValue<Block>(WOS.makeORef("block", props.nodeModel.blockId));
|
|
|
|
let viewModel = getViewModel(props.nodeModel.blockId);
|
2024-08-23 01:25:53 +02:00
|
|
|
if (viewModel == null || viewModel.viewType != blockData?.meta?.view) {
|
2024-08-26 20:56:00 +02:00
|
|
|
viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view);
|
|
|
|
registerViewModel(props.nodeModel.blockId, viewModel);
|
2024-08-23 01:25:53 +02:00
|
|
|
}
|
2024-08-22 00:49:23 +02:00
|
|
|
React.useEffect(() => {
|
|
|
|
return () => {
|
2024-08-26 20:56:00 +02:00
|
|
|
unregisterViewModel(props.nodeModel.blockId);
|
2024-08-22 00:49:23 +02:00
|
|
|
};
|
|
|
|
}, []);
|
2024-08-26 20:56:00 +02:00
|
|
|
if (loading || util.isBlank(props.nodeModel.blockId) || blockData == null) {
|
2024-08-22 00:49:23 +02:00
|
|
|
return null;
|
|
|
|
}
|
2024-07-09 00:04:48 +02:00
|
|
|
if (props.preview) {
|
2024-08-22 00:49:23 +02:00
|
|
|
return <BlockPreview {...props} viewModel={viewModel} />;
|
2024-07-09 00:04:48 +02:00
|
|
|
}
|
2024-08-22 00:49:23 +02:00
|
|
|
return <BlockFull {...props} viewModel={viewModel} />;
|
2024-07-09 00:04:48 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
export { Block };
|