mirror of
https://github.com/wavetermdev/waveterm.git
synced 2025-01-02 18:39:05 +01:00
vdom 5 (#1143)
This commit is contained in:
parent
ac6f9a05d4
commit
416c26c1cd
@ -159,7 +159,7 @@ tasks:
|
|||||||
vars:
|
vars:
|
||||||
GOOS: darwin
|
GOOS: darwin
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
- cp dist/bin/wsh-{{.VERSION}}-darwin.arm64 ~/.waveterm-dev/bin/wsh
|
- cp dist/bin/wsh-{{.VERSION}}-darwin.arm64 ~/Library/Application\ Support/waveterm-dev/bin/wsh
|
||||||
|
|
||||||
build:wsh:internal:
|
build:wsh:internal:
|
||||||
vars:
|
vars:
|
||||||
|
@ -4,16 +4,21 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/wavetermdev/waveterm/pkg/vdom"
|
"github.com/wavetermdev/waveterm/pkg/vdom"
|
||||||
"github.com/wavetermdev/waveterm/pkg/vdom/vdomclient"
|
"github.com/wavetermdev/waveterm/pkg/vdom/vdomclient"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/waveobj"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc"
|
||||||
|
"github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient"
|
||||||
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
"github.com/wavetermdev/waveterm/pkg/wshutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var htmlCmdNewBlock bool
|
var htmlCmdNewBlock bool
|
||||||
|
var GlobalVDomClient *vdomclient.Client
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
htmlCmd.Flags().BoolVarP(&htmlCmdNewBlock, "newblock", "n", false, "create a new block")
|
htmlCmd.Flags().BoolVarP(&htmlCmdNewBlock, "newblock", "n", false, "create a new block")
|
||||||
@ -27,16 +32,116 @@ var htmlCmd = &cobra.Command{
|
|||||||
RunE: htmlRun,
|
RunE: htmlRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func StyleTag(ctx context.Context, props map[string]any) any {
|
||||||
|
return vdom.Bind(`
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.background-inner {
|
||||||
|
max-width: 300px;
|
||||||
|
|
||||||
|
.bg-item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--button-grey-hover-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-preview {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #777;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-label {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
`, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BgItemTag(ctx context.Context, props map[string]any) any {
|
||||||
|
clickFn := func() {
|
||||||
|
log.Printf("bg item clicked %q\n", props["bg"])
|
||||||
|
blockInfo, err := wshclient.BlockInfoCommand(GlobalVDomClient.RpcClient, GlobalVDomClient.RpcContext.BlockId, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error getting block info: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("block info: tabid=%q\n", blockInfo.TabId)
|
||||||
|
err = wshclient.SetMetaCommand(GlobalVDomClient.RpcClient, wshrpc.CommandSetMetaData{
|
||||||
|
ORef: waveobj.ORef{OType: "tab", OID: blockInfo.TabId},
|
||||||
|
Meta: map[string]any{"bg": props["bg"]},
|
||||||
|
}, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error setting meta: %v\n", err)
|
||||||
|
}
|
||||||
|
// wshclient.SetMetaCommand(GlobalVDomClient.RpcClient)
|
||||||
|
}
|
||||||
|
params := map[string]any{
|
||||||
|
"bg": props["bg"],
|
||||||
|
"label": props["label"],
|
||||||
|
"clickHandler": clickFn,
|
||||||
|
}
|
||||||
|
return vdom.Bind(`
|
||||||
|
<div className="bg-item" onClick="#param:clickHandler">
|
||||||
|
<div className="bg-preview" style="background: #param:bg"></div>
|
||||||
|
<div className="bg-label"><bindparam key="label"/></div>
|
||||||
|
</div>`, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllBgItemsTag(ctx context.Context, props map[string]any) any {
|
||||||
|
items := []map[string]any{
|
||||||
|
{"bg": nil, "label": "default"},
|
||||||
|
{"bg": "#ff0000", "label": "red"},
|
||||||
|
{"bg": "#00ff00", "label": "green"},
|
||||||
|
{"bg": "#0000ff", "label": "blue"},
|
||||||
|
}
|
||||||
|
bgElems := make([]*vdom.VDomElem, 0)
|
||||||
|
for _, item := range items {
|
||||||
|
elem := vdom.E("BgItemTag", item)
|
||||||
|
bgElems = append(bgElems, elem)
|
||||||
|
}
|
||||||
|
return vdom.Bind(`
|
||||||
|
<div className="background">
|
||||||
|
<div className="background-inner">
|
||||||
|
<bindparam key="bgElems"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`, map[string]any{"bgElems": bgElems})
|
||||||
|
}
|
||||||
|
|
||||||
func MakeVDom() *vdom.VDomElem {
|
func MakeVDom() *vdom.VDomElem {
|
||||||
vdomStr := `
|
vdomStr := `
|
||||||
<div>
|
<div className="root">
|
||||||
<h1 style="color:red; background-color: #bind:$.bgcolor; border-radius: 4px; padding: 5px;">hello vdom world</h1>
|
<StyleTag/>
|
||||||
<div><bind key="$.text"/> | num[<bind key="$.num"/>]</div>
|
<h1>Set Background</h1>
|
||||||
<div>
|
<div>
|
||||||
<button data-text="hello" onClick='#globalevent:clickinc'>increment</button>
|
<wave:markdown text="*quick vdom application to set background colors*"/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<wave:markdown text="*hello from markdown*"/>
|
<AllBgItemsTag/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
@ -58,11 +163,15 @@ func htmlRun(cmd *cobra.Command, args []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
GlobalVDomClient = client
|
||||||
client.SetGlobalEventHandler(GlobalEventHandler)
|
client.SetGlobalEventHandler(GlobalEventHandler)
|
||||||
log.Printf("created client: %v\n", client)
|
log.Printf("created client: %v\n", client)
|
||||||
client.SetAtomVal("bgcolor", "#0000ff77")
|
client.SetAtomVal("bgcolor", "#0000ff77")
|
||||||
client.SetAtomVal("text", "initial text")
|
client.SetAtomVal("text", "initial text")
|
||||||
client.SetAtomVal("num", 0)
|
client.SetAtomVal("num", 0)
|
||||||
|
client.RegisterComponent("StyleTag", StyleTag)
|
||||||
|
client.RegisterComponent("BgItemTag", BgItemTag)
|
||||||
|
client.RegisterComponent("AllBgItemsTag", AllBgItemsTag)
|
||||||
client.SetRootElem(MakeVDom())
|
client.SetRootElem(MakeVDom())
|
||||||
err = client.CreateVDomContext(&vdom.VDomTarget{NewBlock: htmlCmdNewBlock})
|
err = client.CreateVDomContext(&vdom.VDomTarget{NewBlock: htmlCmdNewBlock})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,8 +5,8 @@ import { BlockComponentModel2, BlockProps } from "@/app/block/blocktypes";
|
|||||||
import { PlotView } from "@/app/view/plotview/plotview";
|
import { PlotView } from "@/app/view/plotview/plotview";
|
||||||
import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview";
|
import { PreviewModel, PreviewView, makePreviewModel } from "@/app/view/preview/preview";
|
||||||
import { SysinfoView, SysinfoViewModel, makeSysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
|
import { SysinfoView, SysinfoViewModel, makeSysinfoViewModel } from "@/app/view/sysinfo/sysinfo";
|
||||||
import { VDomModel } from "@/app/view/term/vdom-model";
|
import { VDomView, makeVDomModel } from "@/app/view/vdom/vdom";
|
||||||
import { VDomView, makeVDomModel } from "@/app/view/vdom/vdom-view";
|
import { VDomModel } from "@/app/view/vdom/vdom-model";
|
||||||
import { ErrorBoundary } from "@/element/errorboundary";
|
import { ErrorBoundary } from "@/element/errorboundary";
|
||||||
import { CenteredDiv } from "@/element/quickelems";
|
import { CenteredDiv } from "@/element/quickelems";
|
||||||
import { NodeModel, useDebouncedNodeInnerRect } from "@/layout/index";
|
import { NodeModel, useDebouncedNodeInnerRect } from "@/layout/index";
|
||||||
|
@ -8,7 +8,7 @@ import { RpcApi } from "@/app/store/wshclientapi";
|
|||||||
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
|
import { makeFeBlockRouteId } from "@/app/store/wshrouter";
|
||||||
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
|
import { DefaultRouter, TabRpcClient } from "@/app/store/wshrpcutil";
|
||||||
import { TermWshClient } from "@/app/view/term/term-wsh";
|
import { TermWshClient } from "@/app/view/term/term-wsh";
|
||||||
import { VDomModel } from "@/app/view/term/vdom-model";
|
import { VDomModel } from "@/app/view/vdom/vdom-model";
|
||||||
import { NodeModel } from "@/layout/index";
|
import { NodeModel } from "@/layout/index";
|
||||||
import {
|
import {
|
||||||
WOS,
|
WOS,
|
||||||
|
58
frontend/app/view/vdom/vdom-utils.tsx
Normal file
58
frontend/app/view/vdom/vdom-utils.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
import { VDomModel } from "@/app/view/vdom/vdom-model";
|
||||||
|
import type { CssNode, List, ListItem } from "css-tree";
|
||||||
|
import * as csstree from "css-tree";
|
||||||
|
|
||||||
|
const TextTag = "#text";
|
||||||
|
|
||||||
|
// TODO support binding
|
||||||
|
export function getTextChildren(elem: VDomElem): string {
|
||||||
|
if (elem.tag == TextTag) {
|
||||||
|
return elem.text;
|
||||||
|
}
|
||||||
|
if (!elem.children) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const textArr = elem.children.map((child) => {
|
||||||
|
return getTextChildren(child);
|
||||||
|
});
|
||||||
|
return textArr.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertVDomId(model: VDomModel, id: string): string {
|
||||||
|
return model.blockId + "::" + id;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function validateAndWrapCss(model: VDomModel, cssText: string, wrapperClassName: string) {
|
||||||
|
try {
|
||||||
|
const ast = csstree.parse(cssText);
|
||||||
|
csstree.walk(ast, {
|
||||||
|
enter(node: CssNode, item: ListItem<CssNode>, list: List<CssNode>) {
|
||||||
|
// Remove disallowed @rules
|
||||||
|
const blockedRules = ["import", "font-face", "keyframes", "namespace", "supports"];
|
||||||
|
if (node.type === "Atrule" && blockedRules.includes(node.name)) {
|
||||||
|
list.remove(item);
|
||||||
|
}
|
||||||
|
// Remove :root selectors
|
||||||
|
if (
|
||||||
|
node.type === "Selector" &&
|
||||||
|
node.children.some((child) => child.type === "PseudoClassSelector" && child.name === "root")
|
||||||
|
) {
|
||||||
|
list.remove(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.type === "IdSelector") {
|
||||||
|
node.name = convertVDomId(model, node.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const sanitizedCss = csstree.generate(ast);
|
||||||
|
return `.${wrapperClassName} { ${sanitizedCss} }`;
|
||||||
|
} catch (error) {
|
||||||
|
// TODO better error handling
|
||||||
|
console.error("CSS processing error:", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
// Copyright 2024, Command Line Inc.
|
|
||||||
// SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
import { VDomRoot } from "@/app/view/term/vdom";
|
|
||||||
import { VDomModel } from "@/app/view/term/vdom-model";
|
|
||||||
import { NodeModel } from "@/layout/index";
|
|
||||||
import { useRef } from "react";
|
|
||||||
|
|
||||||
function makeVDomModel(blockId: string, nodeModel: NodeModel): VDomModel {
|
|
||||||
return new VDomModel(blockId, nodeModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
type VDomViewProps = {
|
|
||||||
model: VDomModel;
|
|
||||||
blockId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function VDomView({ blockId, model }: VDomViewProps) {
|
|
||||||
let viewRef = useRef(null);
|
|
||||||
model.viewRef = viewRef;
|
|
||||||
return (
|
|
||||||
<div className="vdom-view" ref={viewRef}>
|
|
||||||
<VDomRoot model={model} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { makeVDomModel, VDomView };
|
|
5
frontend/app/view/vdom/vdom.less
Normal file
5
frontend/app/view/vdom/vdom.less
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// Copyright 2024, Command Line Inc.
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
.view-vdom {
|
||||||
|
}
|
@ -2,16 +2,22 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
import { Markdown } from "@/app/element/markdown";
|
import { Markdown } from "@/app/element/markdown";
|
||||||
import { VDomModel } from "@/app/view/term/vdom-model";
|
import { VDomModel } from "@/app/view/vdom/vdom-model";
|
||||||
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
import { adaptFromReactOrNativeKeyEvent, checkKeyPressed } from "@/util/keyutil";
|
||||||
|
import clsx from "clsx";
|
||||||
import debug from "debug";
|
import debug from "debug";
|
||||||
import * as jotai from "jotai";
|
import * as jotai from "jotai";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { convertVDomId, getTextChildren, validateAndWrapCss } from "@/app/view/vdom/vdom-utils";
|
||||||
|
import { NodeModel } from "@/layout/index";
|
||||||
|
import "./vdom.less";
|
||||||
|
|
||||||
const TextTag = "#text";
|
const TextTag = "#text";
|
||||||
const FragmentTag = "#fragment";
|
const FragmentTag = "#fragment";
|
||||||
const WaveTextTag = "wave:text";
|
const WaveTextTag = "wave:text";
|
||||||
const WaveNullTag = "wave:null";
|
const WaveNullTag = "wave:null";
|
||||||
|
const StyleTagName = "style";
|
||||||
|
|
||||||
const VDomObjType_Ref = "ref";
|
const VDomObjType_Ref = "ref";
|
||||||
const VDomObjType_Binding = "binding";
|
const VDomObjType_Binding = "binding";
|
||||||
@ -25,7 +31,7 @@ const WaveTagMap: Record<string, VDomReactTagType> = {
|
|||||||
"wave:markdown": WaveMarkdown,
|
"wave:markdown": WaveMarkdown,
|
||||||
};
|
};
|
||||||
|
|
||||||
const AllowedTags: { [tagName: string]: boolean } = {
|
const AllowedSimpleTags: { [tagName: string]: boolean } = {
|
||||||
div: true,
|
div: true,
|
||||||
b: true,
|
b: true,
|
||||||
i: true,
|
i: true,
|
||||||
@ -49,6 +55,30 @@ const AllowedTags: { [tagName: string]: boolean } = {
|
|||||||
select: true,
|
select: true,
|
||||||
option: true,
|
option: true,
|
||||||
form: true,
|
form: true,
|
||||||
|
label: true,
|
||||||
|
table: true,
|
||||||
|
thead: true,
|
||||||
|
tbody: true,
|
||||||
|
tr: true,
|
||||||
|
th: true,
|
||||||
|
td: true,
|
||||||
|
hr: true,
|
||||||
|
br: true,
|
||||||
|
pre: true,
|
||||||
|
code: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const IdAttributes = {
|
||||||
|
id: true,
|
||||||
|
for: true,
|
||||||
|
"aria-labelledby": true,
|
||||||
|
"aria-describedby": true,
|
||||||
|
"aria-controls": true,
|
||||||
|
"aria-owns": true,
|
||||||
|
form: true,
|
||||||
|
headers: true,
|
||||||
|
usemap: true,
|
||||||
|
list: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
function convertVDomFunc(model: VDomModel, fnDecl: VDomFunc, compId: string, propName: string): (e: any) => void {
|
function convertVDomFunc(model: VDomModel, fnDecl: VDomFunc, compId: string, propName: string): (e: any) => void {
|
||||||
@ -165,6 +195,10 @@ function convertProps(elem: VDomElem, model: VDomModel): [GenericPropsType, Set<
|
|||||||
}
|
}
|
||||||
// fallthrough to set props[key] = val
|
// fallthrough to set props[key] = val
|
||||||
}
|
}
|
||||||
|
if (IdAttributes[key]) {
|
||||||
|
props[key] = convertVDomId(model, val);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
props[key] = val;
|
props[key] = val;
|
||||||
}
|
}
|
||||||
return [props, atomKeys];
|
return [props, atomKeys];
|
||||||
@ -223,6 +257,20 @@ function WaveMarkdown({ elem, model }: { elem: VDomElem; model: VDomModel }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function StyleTag({ elem, model }: { elem: VDomElem; model: VDomModel }) {
|
||||||
|
const styleText = getTextChildren(elem);
|
||||||
|
if (styleText == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const wrapperClassName = "vdom-" + model.blockId;
|
||||||
|
// TODO handle errors
|
||||||
|
const sanitizedCss = validateAndWrapCss(model, styleText, wrapperClassName);
|
||||||
|
if (sanitizedCss == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <style>{sanitizedCss}</style>;
|
||||||
|
}
|
||||||
|
|
||||||
function VDomTag({ elem, model }: { elem: VDomElem; model: VDomModel }) {
|
function VDomTag({ elem, model }: { elem: VDomElem; model: VDomModel }) {
|
||||||
const props = useVDom(model, elem);
|
const props = useVDom(model, elem);
|
||||||
if (elem.tag == WaveNullTag) {
|
if (elem.tag == WaveNullTag) {
|
||||||
@ -235,7 +283,10 @@ function VDomTag({ elem, model }: { elem: VDomElem; model: VDomModel }) {
|
|||||||
if (waveTag) {
|
if (waveTag) {
|
||||||
return waveTag({ elem, model });
|
return waveTag({ elem, model });
|
||||||
}
|
}
|
||||||
if (!AllowedTags[elem.tag]) {
|
if (elem.tag == StyleTagName) {
|
||||||
|
return <StyleTag elem={elem} model={model} />;
|
||||||
|
}
|
||||||
|
if (!AllowedSimpleTags[elem.tag]) {
|
||||||
return <div>{"Invalid Tag <" + elem.tag + ">"}</div>;
|
return <div>{"Invalid Tag <" + elem.tag + ">"}</div>;
|
||||||
}
|
}
|
||||||
let childrenComps = convertChildren(elem, model);
|
let childrenComps = convertChildren(elem, model);
|
||||||
@ -280,4 +331,24 @@ function VDomRoot({ model }: { model: VDomModel }) {
|
|||||||
return <div className="vdom">{rtn}</div>;
|
return <div className="vdom">{rtn}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { VDomRoot };
|
function makeVDomModel(blockId: string, nodeModel: NodeModel): VDomModel {
|
||||||
|
return new VDomModel(blockId, nodeModel);
|
||||||
|
}
|
||||||
|
|
||||||
|
type VDomViewProps = {
|
||||||
|
model: VDomModel;
|
||||||
|
blockId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function VDomView({ blockId, model }: VDomViewProps) {
|
||||||
|
let viewRef = React.useRef(null);
|
||||||
|
model.viewRef = viewRef;
|
||||||
|
const vdomClass = "vdom-" + blockId;
|
||||||
|
return (
|
||||||
|
<div className={clsx("vdom-view", vdomClass)} ref={viewRef}>
|
||||||
|
<VDomRoot model={model} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { makeVDomModel, VDomView };
|
@ -197,6 +197,10 @@ func makeNullVDom() *vdom.VDomElem {
|
|||||||
return &vdom.VDomElem{WaveId: uuid.New().String(), Tag: vdom.WaveNullTag}
|
return &vdom.VDomElem{WaveId: uuid.New().String(), Tag: vdom.WaveNullTag}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) RegisterComponent(name string, cfunc vdom.CFunc) {
|
||||||
|
c.Root.RegisterComponent(name, cfunc)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) fullRender() (*vdom.VDomBackendUpdate, error) {
|
func (c *Client) fullRender() (*vdom.VDomBackendUpdate, error) {
|
||||||
c.Root.RunWork()
|
c.Root.RunWork()
|
||||||
c.Root.Render(c.RootElem)
|
c.Root.Render(c.RootElem)
|
||||||
|
@ -270,11 +270,30 @@ func DBFindWindowForTabId(ctx context.Context, tabId string) (string, error) {
|
|||||||
|
|
||||||
func DBFindTabForBlockId(ctx context.Context, blockId string) (string, error) {
|
func DBFindTabForBlockId(ctx context.Context, blockId string) (string, error) {
|
||||||
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
|
return WithTxRtn(ctx, func(tx *TxWrap) (string, error) {
|
||||||
query := `
|
iterNum := 1
|
||||||
SELECT t.oid
|
for {
|
||||||
FROM db_tab t, json_each(data->'blockids') je
|
if iterNum > 5 {
|
||||||
WHERE je.value = ?;`
|
return "", fmt.Errorf("too many iterations looking for tab in block parents")
|
||||||
return tx.GetString(query, blockId), nil
|
}
|
||||||
|
query := `
|
||||||
|
SELECT json_extract(b.data, '$.parentoref') AS parentoref
|
||||||
|
FROM db_block b
|
||||||
|
WHERE b.oid = ?;`
|
||||||
|
parentORef := tx.GetString(query, blockId)
|
||||||
|
oref, err := waveobj.ParseORef(parentORef)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("bad block parent oref: %v", err)
|
||||||
|
}
|
||||||
|
if oref.OType == "tab" {
|
||||||
|
return oref.OID, nil
|
||||||
|
}
|
||||||
|
if oref.OType == "block" {
|
||||||
|
blockId = oref.OID
|
||||||
|
iterNum++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("bad parent oref type: %v", oref.OType)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user