Skip to content

Commit

Permalink
vdom terminal toolbar (#1263)
Browse files Browse the repository at this point in the history
  • Loading branch information
sawka authored Nov 11, 2024
1 parent 83f671c commit 3fc45c6
Show file tree
Hide file tree
Showing 12 changed files with 181 additions and 19 deletions.
4 changes: 4 additions & 0 deletions frontend/app/block/block.less
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
overflow: hidden;
min-height: 0;
padding: 5px;

&.block-no-padding {
padding: 0;
}
}

.block-focuselem {
Expand Down
14 changes: 11 additions & 3 deletions frontend/app/block/block.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ import {
} from "@/store/global";
import { getWaveObjectAtom, makeORef, useWaveObjectValue } from "@/store/wos";
import { focusedBlockId, getElemAsStr } from "@/util/focusutil";
import { isBlank } from "@/util/util";
import { isBlank, useAtomValueSafe } from "@/util/util";
import { HelpView, HelpViewModel, makeHelpViewModel } from "@/view/helpview/helpview";
import { QuickTipsView, QuickTipsViewModel } from "@/view/quicktipsview/quicktipsview";
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 clsx from "clsx";
import { atom, useAtomValue } from "jotai";
import { Suspense, memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import "./block.less";
Expand Down Expand Up @@ -154,11 +155,12 @@ const BlockSubBlock = memo(({ nodeModel, viewModel }: FullSubBlockProps) => {
() => getViewElem(nodeModel.blockId, blockRef, contentRef, blockData?.meta?.view, viewModel),
[nodeModel.blockId, blockData?.meta?.view, viewModel]
);
const noPadding = useAtomValueSafe(viewModel.noPadding);
if (!blockData) {
return null;
}
return (
<div key="content" className="block-content" ref={contentRef}>
<div key="content" className={clsx("block-content", { "block-no-padding": noPadding })} ref={contentRef}>
<ErrorBoundary>
<Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{viewElem}</Suspense>
</ErrorBoundary>
Expand All @@ -176,6 +178,7 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
const isFocused = useAtomValue(nodeModel.isFocused);
const disablePointerEvents = useAtomValue(nodeModel.disablePointerEvents);
const innerRect = useDebouncedNodeInnerRect(nodeModel);
const noPadding = useAtomValueSafe(viewModel.noPadding);

useLayoutEffect(() => {
setBlockClicked(isFocused);
Expand Down Expand Up @@ -273,7 +276,12 @@ const BlockFull = memo(({ nodeModel, viewModel }: FullBlockProps) => {
onChange={() => {}}
/>
</div>
<div key="content" className="block-content" ref={contentRef} style={blockContentStyle}>
<div
key="content"
className={clsx("block-content", { "block-no-padding": noPadding })}
ref={contentRef}
style={blockContentStyle}
>
<ErrorBoundary>
<Suspense fallback={<CenteredDiv>Loading...</CenteredDiv>}>{viewElem}</Suspense>
</ErrorBoundary>
Expand Down
29 changes: 29 additions & 0 deletions frontend/app/view/term/term-wsh.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ export class TermWshClient extends WshClient {
magnified: data.target?.magnified,
});
return oref;
} else if (data.target?.toolbar?.toolbar) {
const oldVDomBlockId = globalStore.get(this.model.vdomToolbarBlockId);
console.log("vdom:toolbar", data.target.toolbar);
globalStore.set(this.model.vdomToolbarTarget, data.target.toolbar);
const oref = await RpcApi.CreateSubBlockCommand(this, {
parentblockid: this.blockId,
blockdef: {
meta: {
view: "vdom",
"vdom:route": rh.getSource(),
},
},
});
const [_, newVDomBlockId] = splitORef(oref);
if (!isBlank(oldVDomBlockId)) {
// dispose of the old vdom block
setTimeout(() => {
RpcApi.DeleteSubBlockCommand(this, { blockid: oldVDomBlockId });
}, 500);
}
setTimeout(() => {
RpcApi.SetMetaCommand(this, {
oref: makeORef("block", this.model.blockId),
meta: {
"term:vdomtoolbarblockid": newVDomBlockId,
},
});
}, 50);
return oref;
} else {
// in the terminal
// check if there is a current active vdom block
Expand Down
11 changes: 9 additions & 2 deletions frontend/app/view/term/term.less
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@

.view-term {
display: flex;
flex-direction: row;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
padding-left: 4px;
position: relative;

.term-header {
Expand All @@ -31,11 +30,19 @@
border-bottom: 1px solid var(--border-color);
}

.term-toolbar {
height: 20px;
border-bottom: 1px solid var(--border-color);
overflow: hidden;
}

.term-connectelem {
flex-grow: 1;
min-height: 0;
overflow: hidden;
line-height: 1;
margin: 5px;
margin-left: 4px;
}

.term-htmlelem {
Expand Down
87 changes: 85 additions & 2 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ class TermViewModel {
termWshClient: TermWshClient;
shellProcStatusRef: React.MutableRefObject<string>;
vdomBlockId: jotai.Atom<string>;
vdomToolbarBlockId: jotai.Atom<string>;
vdomToolbarTarget: jotai.PrimitiveAtom<VDomTargetToolbar>;
fontSizeAtom: jotai.Atom<number>;
termThemeNameAtom: jotai.Atom<string>;
noPadding: jotai.PrimitiveAtom<boolean>;

constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "term";
Expand All @@ -70,6 +73,11 @@ class TermViewModel {
const blockData = get(this.blockAtom);
return blockData?.meta?.["term:vdomblockid"];
});
this.vdomToolbarBlockId = jotai.atom((get) => {
const blockData = get(this.blockAtom);
return blockData?.meta?.["term:vdomtoolbarblockid"];
});
this.vdomToolbarTarget = jotai.atom<VDomTargetToolbar>(null) as jotai.PrimitiveAtom<VDomTargetToolbar>;
this.termMode = jotai.atom((get) => {
const blockData = get(this.blockAtom);
return blockData?.meta?.["term:mode"] ?? "term";
Expand Down Expand Up @@ -167,6 +175,7 @@ class TermViewModel {
return blockData?.meta?.["term:theme"] ?? get(settingsKeyAtom) ?? "default-dark";
});
});
this.noPadding = jotai.atom(true);
}

setTermMode(mode: "term" | "vdom") {
Expand All @@ -191,6 +200,18 @@ class TermViewModel {
return bcm.viewModel as VDomModel;
}

getVDomToolbarModel(): VDomModel {
const vdomToolbarBlockId = globalStore.get(this.vdomToolbarBlockId);
if (!vdomToolbarBlockId) {
return null;
}
const bcm = getBlockComponentModel(vdomToolbarBlockId);
if (!bcm) {
return null;
}
return bcm.viewModel as VDomModel;
}

dispose() {
DefaultRouter.unregisterRoute(makeFeBlockRouteId(this.blockId));
}
Expand Down Expand Up @@ -347,6 +368,15 @@ class TermViewModel {
prtn.catch((e) => console.log("error controller resync (force restart)", e));
},
});
if (blockData?.meta?.["term:vdomtoolbarblockid"]) {
fullMenu.push({ type: "separator" });
fullMenu.push({
label: "Close Toolbar",
click: () => {
RpcApi.DeleteSubBlockCommand(TabRpcClient, { blockid: blockData.meta["term:vdomtoolbarblockid"] });
},
});
}
return fullMenu;
}
}
Expand Down Expand Up @@ -382,6 +412,44 @@ const TermResyncHandler = React.memo(({ blockId, model }: TerminalViewProps) =>
return null;
});

const TermVDomToolbarNode = ({ vdomBlockId, blockId, model }: TerminalViewProps & { vdomBlockId: string }) => {
React.useEffect(() => {
const unsub = waveEventSubscribe({
eventType: "blockclose",
scope: WOS.makeORef("block", vdomBlockId),
handler: (event) => {
RpcApi.SetMetaCommand(TabRpcClient, {
oref: WOS.makeORef("block", blockId),
meta: {
"term:mode": null,
"term:vdomtoolbarblockid": null,
},
});
},
});
return () => {
unsub();
};
}, []);
let vdomNodeModel = {
blockId: vdomBlockId,
isFocused: jotai.atom(false),
focusNode: () => {},
onClose: () => {
if (vdomBlockId != null) {
RpcApi.DeleteSubBlockCommand(TabRpcClient, { blockid: vdomBlockId });
}
},
};
const toolbarTarget = jotai.useAtomValue(model.vdomToolbarTarget);
const heightStr = toolbarTarget?.height ?? "1.5em";
return (
<div key="vdomToolbar" className="term-toolbar" style={{ height: heightStr }}>
<SubBlock key="vdom" nodeModel={vdomNodeModel} />
</div>
);
};

const TermVDomNodeSingleId = ({ vdomBlockId, blockId, model }: TerminalViewProps & { vdomBlockId: string }) => {
React.useEffect(() => {
const unsub = waveEventSubscribe({
Expand Down Expand Up @@ -431,6 +499,21 @@ const TermVDomNode = ({ blockId, model }: TerminalViewProps) => {
return <TermVDomNodeSingleId key={vdomBlockId} vdomBlockId={vdomBlockId} blockId={blockId} model={model} />;
};

const TermToolbarVDomNode = ({ blockId, model }: TerminalViewProps) => {
const vdomToolbarBlockId = jotai.useAtomValue(model.vdomToolbarBlockId);
if (vdomToolbarBlockId == null) {
return null;
}
return (
<TermVDomToolbarNode
key={vdomToolbarBlockId}
vdomBlockId={vdomToolbarBlockId}
blockId={blockId}
model={model}
/>
);
};

const TerminalView = ({ blockId, model }: TerminalViewProps) => {
const viewRef = React.useRef<HTMLDivElement>(null);
const connectElemRef = React.useRef<HTMLDivElement>(null);
Expand Down Expand Up @@ -547,14 +630,14 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
cols: termRef.current?.terminal.cols ?? 80,
blockId: blockId,
};

return (
<div className={clsx("view-term", "term-mode-" + termMode)} ref={viewRef}>
<TermResyncHandler blockId={blockId} model={model} />
<TermThemeUpdater blockId={blockId} termRef={termRef} />
<TermStickers config={stickerConfig} />
<div key="conntectElem" className="term-connectelem" ref={connectElemRef}></div>
<TermToolbarVDomNode key="vdom-toolbar" blockId={blockId} model={model} />
<TermVDomNode key="vdom" blockId={blockId} model={model} />
<div key="conntectElem" className="term-connectelem" ref={connectElemRef}></div>
</div>
);
};
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/view/vdom/vdom-model.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export class VDomModel {
refOutputStore: Map<string, any> = new Map();
globalVersion: jotai.PrimitiveAtom<number> = jotai.atom(0);
hasBackendWork: boolean = false;
noPadding: jotai.PrimitiveAtom<boolean>;

constructor(blockId: string, nodeModel: BlockNodeModel) {
this.viewType = "vdom";
Expand All @@ -147,6 +148,7 @@ export class VDomModel {
const blockData = get(WOS.getWaveObjectAtom<Block>(makeORef("block", this.blockId)));
return blockData?.meta?.["vdom:route"];
});
this.noPadding = jotai.atom(true);
this.persist = getBlockMetaKeyAtom(this.blockId, "vdom:persist");
this.wshClient = new VDomWshClient(this);
DefaultRouter.registerRoute(this.wshClient.routeId, this.wshClient);
Expand Down
1 change: 1 addition & 0 deletions frontend/types/custom.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,7 @@ declare global {
endIconButtons?: jotai.Atom<IconButtonDecl[]>;
blockBg?: jotai.Atom<MetaType>;
manageConnection?: jotai.Atom<boolean>;
noPadding?: jotai.Atom<boolean>;

onBack?: () => void;
onForward?: () => void;
Expand Down
8 changes: 8 additions & 0 deletions frontend/types/gotypes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ declare global {
"term:localshellopts"?: string[];
"term:scrollback"?: number;
"term:vdomblockid"?: string;
"term:vdomtoolbarblockid"?: string;
"vdom:*"?: boolean;
"vdom:initialized"?: boolean;
"vdom:correlationid"?: string;
Expand Down Expand Up @@ -795,6 +796,13 @@ declare global {
type VDomTarget = {
newblock?: boolean;
magnified?: boolean;
toolbar?: VDomTargetToolbar;
};

// vdom.VDomTargetToolbar
type VDomTargetToolbar = {
toolbar: boolean;
height?: string;
};

// vdom.VDomTransferElem
Expand Down
10 changes: 8 additions & 2 deletions pkg/vdom/vdom_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,14 @@ type VDomMessage struct {
// target -- to support new targets in the future, like toolbars, partial blocks, splits, etc.
// default is vdom context inside of a terminal block
type VDomTarget struct {
NewBlock bool `json:"newblock,omitempty"`
Magnified bool `json:"magnified,omitempty"`
NewBlock bool `json:"newblock,omitempty"`
Magnified bool `json:"magnified,omitempty"`
Toolbar *VDomTargetToolbar `json:"toolbar,omitempty"`
}

type VDomTargetToolbar struct {
Toolbar bool `json:"toolbar"`
Height string `json:"height,omitempty"`
}

// matches WaveKeyboardEvent
Expand Down
14 changes: 13 additions & 1 deletion pkg/vdom/vdomclient/vdomclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type AppOpts struct {
GlobalStyles []byte
RootComponentName string // defaults to "App"
NewBlockFlag string // defaults to "n" (set to "-" to disable)
TargetNewBlock bool
TargetToolbar *vdom.VDomTargetToolbar
}

type Client struct {
Expand Down Expand Up @@ -116,7 +118,17 @@ func (client *Client) runMainE() error {
if err != nil {
return err
}
err = client.CreateVDomContext(&vdom.VDomTarget{NewBlock: client.NewBlockFlag})
target := &vdom.VDomTarget{}
if client.AppOpts.TargetNewBlock || client.NewBlockFlag {
target.NewBlock = client.NewBlockFlag
}
if client.AppOpts.TargetToolbar != nil {
target.Toolbar = client.AppOpts.TargetToolbar
}
if target.NewBlock && target.Toolbar != nil {
return fmt.Errorf("cannot specify both new block and toolbar target")
}
err = client.CreateVDomContext(target)
if err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions pkg/waveobj/metaconsts.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const (
MetaKey_TermLocalShellOpts = "term:localshellopts"
MetaKey_TermScrollback = "term:scrollback"
MetaKey_TermVDomSubBlockId = "term:vdomblockid"
MetaKey_TermVDomToolbarBlockId = "term:vdomtoolbarblockid"

MetaKey_VDomClear = "vdom:*"
MetaKey_VDomInitialized = "vdom:initialized"
Expand Down
Loading

0 comments on commit 3fc45c6

Please sign in to comment.