From a782d836286a98c6916d9936360a0892e8dc0c8d Mon Sep 17 00:00:00 2001 From: Mike Sawka Date: Fri, 25 Oct 2024 18:36:09 -0700 Subject: [PATCH] vdom 6 (#1145) --- frontend/app/block/block.tsx | 49 +++++++++++++++++-------- frontend/app/block/blocktypes.ts | 25 ++++++++++++- frontend/app/element/quicktips.less | 2 +- frontend/app/view/helpview/helpview.tsx | 6 +-- frontend/app/view/preview/preview.tsx | 8 ++-- frontend/app/view/term/term.tsx | 15 +++++--- frontend/app/view/vdom/vdom-model.tsx | 6 +-- frontend/app/view/vdom/vdom.tsx | 4 +- frontend/app/view/webview/webview.tsx | 8 ++-- 9 files changed, 84 insertions(+), 39 deletions(-) diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx index 7b70678a1..6390ecbea 100644 --- a/frontend/app/block/block.tsx +++ b/frontend/app/block/block.tsx @@ -1,7 +1,14 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { BlockComponentModel2, BlockProps } from "@/app/block/blocktypes"; +import { + BlockComponentModel2, + BlockNodeModel, + BlockProps, + FullBlockProps, + FullSubBlockProps, + SubBlockProps, +} from "@/app/block/blocktypes"; import { PlotView } from "@/app/view/plotview/plotview"; import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview"; import { SysinfoView, SysinfoViewModel, makeSysinfoViewModel } from "@/app/view/sysinfo/sysinfo"; @@ -9,7 +16,7 @@ import { VDomView, makeVDomModel } from "@/app/view/vdom/vdom"; import { VDomModel } from "@/app/view/vdom/vdom-model"; import { ErrorBoundary } from "@/element/errorboundary"; import { CenteredDiv } from "@/element/quickelems"; -import { NodeModel, useDebouncedNodeInnerRect } from "@/layout/index"; +import { useDebouncedNodeInnerRect } from "@/layout/index"; import { counterInc, getBlockComponentModel, @@ -30,14 +37,7 @@ import "./block.less"; import { BlockFrame } from "./blockframe"; import { blockViewToIcon, blockViewToName } from "./blockutil"; -type FullBlockProps = { - isSubBlock?: boolean; - preview: boolean; - nodeModel: NodeModel; - viewModel: ViewModel; -}; - -function makeViewModel(blockId: string, blockView: string, nodeModel: NodeModel): ViewModel { +function makeViewModel(blockId: string, blockView: string, nodeModel: BlockNodeModel): ViewModel { if (blockView === "term") { return makeTerminalModel(blockId, nodeModel); } @@ -146,7 +146,7 @@ const BlockPreview = memo(({ nodeModel, viewModel }: FullBlockProps) => { ); }); -const BlockSubBlock = memo(({ nodeModel, viewModel }: FullBlockProps) => { +const BlockSubBlock = memo(({ nodeModel, viewModel }: FullSubBlockProps) => { const [blockData] = useWaveObjectValue(makeORef("block", nodeModel.blockId)); const blockRef = useRef(null); const contentRef = useRef(null); @@ -304,10 +304,29 @@ const Block = memo((props: BlockProps) => { if (props.preview) { return ; } - if (props.isSubBlock) { - return ; - } return ; }); -export { Block }; +const SubBlock = memo((props: SubBlockProps) => { + counterInc("render-Block"); + counterInc("render-Block-" + props.nodeModel?.blockId?.substring(0, 8)); + const [blockData, loading] = useWaveObjectValue(makeORef("block", props.nodeModel.blockId)); + const bcm = getBlockComponentModel(props.nodeModel.blockId); + let viewModel = bcm?.viewModel; + if (viewModel == null || viewModel.viewType != blockData?.meta?.view) { + viewModel = makeViewModel(props.nodeModel.blockId, blockData?.meta?.view, props.nodeModel); + registerBlockComponentModel(props.nodeModel.blockId, { viewModel }); + } + useEffect(() => { + return () => { + unregisterBlockComponentModel(props.nodeModel.blockId); + viewModel?.dispose?.(); + }; + }, []); + if (loading || isBlank(props.nodeModel.blockId) || blockData == null) { + return null; + } + return ; +}); + +export { Block, SubBlock }; diff --git a/frontend/app/block/blocktypes.ts b/frontend/app/block/blocktypes.ts index 03322348b..97126c5eb 100644 --- a/frontend/app/block/blocktypes.ts +++ b/frontend/app/block/blocktypes.ts @@ -2,12 +2,35 @@ // SPDX-License-Identifier: Apache-2.0 import { NodeModel } from "@/layout/index"; +import { Atom } from "jotai"; + +export interface BlockNodeModel { + blockId: string; + isFocused: Atom; + onClose: () => void; + focusNode: () => void; +} + +export type FullBlockProps = { + preview: boolean; + nodeModel: NodeModel; + viewModel: ViewModel; +}; + export interface BlockProps { - isSubBlock?: boolean; preview: boolean; nodeModel: NodeModel; } +export type FullSubBlockProps = { + nodeModel: BlockNodeModel; + viewModel: ViewModel; +}; + +export interface SubBlockProps { + nodeModel: BlockNodeModel; +} + export interface BlockComponentModel2 { onClick?: () => void; onFocusCapture?: React.FocusEventHandler; diff --git a/frontend/app/element/quicktips.less b/frontend/app/element/quicktips.less index 34a574bf5..e3ff547e8 100644 --- a/frontend/app/element/quicktips.less +++ b/frontend/app/element/quicktips.less @@ -54,7 +54,7 @@ font: var(--fixed-font); font-size: 0.85em; color: var(--keybinding-color); - background-color: var(--keybinding-bg-color); + background-color: var(--highlight-bg-color); border-radius: 4px; border: 1px solid var(--keybinding-border-color); box-shadow: none; diff --git a/frontend/app/view/helpview/helpview.tsx b/frontend/app/view/helpview/helpview.tsx index 5f45c45b7..f843c6ca9 100644 --- a/frontend/app/view/helpview/helpview.tsx +++ b/frontend/app/view/helpview/helpview.tsx @@ -1,16 +1,16 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockNodeModel } from "@/app/block/blocktypes"; import { getApi } from "@/app/store/global"; import { WebView, WebViewModel } from "@/app/view/webview/webview"; -import { NodeModel } from "@/layout/index"; import { fireAndForget } from "@/util/util"; import { atom, useAtomValue } from "jotai"; import { useCallback } from "react"; import "./helpview.less"; class HelpViewModel extends WebViewModel { - constructor(blockId: string, nodeModel: NodeModel) { + constructor(blockId: string, nodeModel: BlockNodeModel) { super(blockId, nodeModel); this.getSettingsMenuItems = undefined; this.viewText = atom((get) => { @@ -44,7 +44,7 @@ class HelpViewModel extends WebViewModel { } } -function makeHelpViewModel(blockId: string, nodeModel: NodeModel) { +function makeHelpViewModel(blockId: string, nodeModel: BlockNodeModel) { return new HelpViewModel(blockId, nodeModel); } diff --git a/frontend/app/view/preview/preview.tsx b/frontend/app/view/preview/preview.tsx index 66598b829..f0e1a67f9 100644 --- a/frontend/app/view/preview/preview.tsx +++ b/frontend/app/view/preview/preview.tsx @@ -1,6 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockNodeModel } from "@/app/block/blocktypes"; import { CenteredDiv } from "@/app/element/quickelems"; import { TypeAheadModal } from "@/app/modals/typeaheadmodal"; import { ContextMenuModel } from "@/app/store/contextmenu"; @@ -9,7 +10,6 @@ import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; import { CodeEditor } from "@/app/view/codeeditor/codeeditor"; import { Markdown } from "@/element/markdown"; -import { NodeModel } from "@/layout/index"; import { atoms, createBlock, getConnStatusAtom, getSettingsKeyAtom, globalStore, refocusNode } from "@/store/global"; import * as services from "@/store/services"; import * as WOS from "@/store/wos"; @@ -98,7 +98,7 @@ function isStreamingType(mimeType: string): boolean { export class PreviewModel implements ViewModel { viewType: string; blockId: string; - nodeModel: NodeModel; + nodeModel: BlockNodeModel; blockAtom: Atom; viewIcon: Atom; viewName: Atom; @@ -141,7 +141,7 @@ export class PreviewModel implements ViewModel { directoryKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean; codeEditKeyDownHandler: (waveEvent: WaveKeyboardEvent) => boolean; - constructor(blockId: string, nodeModel: NodeModel) { + constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewType = "preview"; this.blockId = blockId; this.nodeModel = nodeModel; @@ -733,7 +733,7 @@ export class PreviewModel implements ViewModel { } } -function makePreviewModel(blockId: string, nodeModel: NodeModel): PreviewModel { +function makePreviewModel(blockId: string, nodeModel: BlockNodeModel): PreviewModel { const previewModel = new PreviewModel(blockId, nodeModel); return previewModel; } diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx index eab67e915..aa1f30359 100644 --- a/frontend/app/view/term/term.tsx +++ b/frontend/app/view/term/term.tsx @@ -1,7 +1,8 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { Block } from "@/app/block/block"; +import { Block, SubBlock } from "@/app/block/block"; +import { BlockNodeModel } from "@/app/block/blocktypes"; import { getAllGlobalKeyBindings } from "@/app/store/keymodel"; import { waveEventSubscribe } from "@/app/store/wps"; import { RpcApi } from "@/app/store/wshclientapi"; @@ -9,7 +10,6 @@ import { makeFeBlockRouteId } from "@/app/store/wshrouter"; import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; import { TermWshClient } from "@/app/view/term/term-wsh"; import { VDomModel } from "@/app/view/vdom/vdom-model"; -import { NodeModel } from "@/layout/index"; import { WOS, atoms, @@ -41,7 +41,7 @@ type InitialLoadDataType = { class TermViewModel { viewType: string; - nodeModel: NodeModel; + nodeModel: BlockNodeModel; connected: boolean; termRef: React.RefObject; blockAtom: jotai.Atom; @@ -59,7 +59,7 @@ class TermViewModel { fontSizeAtom: jotai.Atom; termThemeNameAtom: jotai.Atom; - constructor(blockId: string, nodeModel: NodeModel) { + constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewType = "term"; this.blockId = blockId; this.termWshClient = new TermWshClient(blockId, this); @@ -351,7 +351,7 @@ class TermViewModel { } } -function makeTerminalModel(blockId: string, nodeModel: NodeModel): TermViewModel { +function makeTerminalModel(blockId: string, nodeModel: BlockNodeModel): TermViewModel { return new TermViewModel(blockId, nodeModel); } @@ -407,6 +407,9 @@ const TermVDomNodeSingleId = ({ vdomBlockId, blockId, model }: TerminalViewProps let vdomNodeModel = { blockId: vdomBlockId, isFocused: isFocusedAtom, + focusNode: () => { + model.nodeModel.focusNode(); + }, onClose: () => { if (vdomBlockId != null) { RpcApi.DeleteSubBlockCommand(TabRpcClient, { blockid: vdomBlockId }); @@ -415,7 +418,7 @@ const TermVDomNodeSingleId = ({ vdomBlockId, blockId, model }: TerminalViewProps }; return (
- +
); }; diff --git a/frontend/app/view/vdom/vdom-model.tsx b/frontend/app/view/vdom/vdom-model.tsx index c4ce4d974..07f301fdb 100644 --- a/frontend/app/view/vdom/vdom-model.tsx +++ b/frontend/app/view/vdom/vdom-model.tsx @@ -1,6 +1,7 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockNodeModel } from "@/app/block/blocktypes"; import { getBlockMetaKeyAtom, globalStore, WOS } from "@/app/store/global"; import { makeORef } from "@/app/store/wos"; import { waveEventSubscribe } from "@/app/store/wps"; @@ -8,7 +9,6 @@ import { RpcResponseHelper, WshClient } from "@/app/store/wshclient"; import { RpcApi } from "@/app/store/wshclientapi"; import { makeFeBlockRouteId } from "@/app/store/wshrouter"; import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil"; -import { NodeModel } from "@/layout/index"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import debug from "debug"; import * as jotai from "jotai"; @@ -79,7 +79,7 @@ class VDomWshClient extends WshClient { export class VDomModel { blockId: string; - nodeModel: NodeModel; + nodeModel: BlockNodeModel; viewType: string; viewIcon: jotai.Atom; viewName: jotai.Atom; @@ -109,7 +109,7 @@ export class VDomModel { routeGoneUnsub: () => void; routeConfirmed: boolean = false; - constructor(blockId: string, nodeModel: NodeModel) { + constructor(blockId: string, nodeModel: BlockNodeModel) { this.viewType = "vdom"; this.blockId = blockId; this.nodeModel = nodeModel; diff --git a/frontend/app/view/vdom/vdom.tsx b/frontend/app/view/vdom/vdom.tsx index 8f8706c05..08adfee7e 100644 --- a/frontend/app/view/vdom/vdom.tsx +++ b/frontend/app/view/vdom/vdom.tsx @@ -9,8 +9,8 @@ import debug from "debug"; import * as jotai from "jotai"; import * as React from "react"; +import { BlockNodeModel } from "@/app/block/blocktypes"; import { convertVDomId, getTextChildren, validateAndWrapCss } from "@/app/view/vdom/vdom-utils"; -import { NodeModel } from "@/layout/index"; import "./vdom.less"; const TextTag = "#text"; @@ -331,7 +331,7 @@ function VDomRoot({ model }: { model: VDomModel }) { return
{rtn}
; } -function makeVDomModel(blockId: string, nodeModel: NodeModel): VDomModel { +function makeVDomModel(blockId: string, nodeModel: BlockNodeModel): VDomModel { return new VDomModel(blockId, nodeModel); } diff --git a/frontend/app/view/webview/webview.tsx b/frontend/app/view/webview/webview.tsx index 1b333d4ff..42fa739e5 100644 --- a/frontend/app/view/webview/webview.tsx +++ b/frontend/app/view/webview/webview.tsx @@ -1,12 +1,12 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 +import { BlockNodeModel } from "@/app/block/blocktypes"; import { getApi, getSettingsKeyAtom, openLink } from "@/app/store/global"; import { getSimpleControlShiftAtom } from "@/app/store/keymodel"; import { ObjectService } from "@/app/store/services"; import { RpcApi } from "@/app/store/wshclientapi"; import { TabRpcClient } from "@/app/store/wshrpcutil"; -import { NodeModel } from "@/layout/index"; import { WOS, globalStore } from "@/store/global"; import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil"; import { fireAndForget } from "@/util/util"; @@ -44,12 +44,12 @@ export class WebViewModel implements ViewModel { refreshIcon: PrimitiveAtom; webviewRef: React.RefObject; urlInputRef: React.RefObject; - nodeModel: NodeModel; + nodeModel: BlockNodeModel; endIconButtons?: Atom; mediaPlaying: PrimitiveAtom; mediaMuted: PrimitiveAtom; - constructor(blockId: string, nodeModel: NodeModel) { + constructor(blockId: string, nodeModel: BlockNodeModel) { this.nodeModel = nodeModel; this.viewType = "web"; this.blockId = blockId; @@ -459,7 +459,7 @@ export class WebViewModel implements ViewModel { } } -function makeWebViewModel(blockId: string, nodeModel: NodeModel): WebViewModel { +function makeWebViewModel(blockId: string, nodeModel: BlockNodeModel): WebViewModel { const webviewModel = new WebViewModel(blockId, nodeModel); return webviewModel; }