diff --git a/cmd/wsh/cmd/wshcmd-view.go b/cmd/wsh/cmd/wshcmd-view.go
index 2e198f6b1..1a5ddc995 100644
--- a/cmd/wsh/cmd/wshcmd-view.go
+++ b/cmd/wsh/cmd/wshcmd-view.go
@@ -47,9 +47,9 @@ func viewRun(cmd *cobra.Command, args []string) {
wshutil.SetTermRawModeAndInstallShutdownHandlers(true)
viewWshCmd := &wshrpc.CommandCreateBlockData{
BlockDef: &wstore.BlockDef{
- View: "preview",
Meta: map[string]interface{}{
- "file": absFile,
+ wstore.MetaKey_View: "preview",
+ wstore.MetaKey_File: absFile,
},
},
}
diff --git a/frontend/app/app.tsx b/frontend/app/app.tsx
index 364f130d7..8c7c757ca 100644
--- a/frontend/app/app.tsx
+++ b/frontend/app/app.tsx
@@ -313,11 +313,11 @@ const AppInner = () => {
function handleKeyDown(waveEvent: WaveKeyboardEvent): boolean {
// global key handler for now (refactor later)
- if (keyutil.checkKeyPressed(waveEvent, "Cmd:]")) {
+ if (keyutil.checkKeyPressed(waveEvent, "Cmd:]") || keyutil.checkKeyPressed(waveEvent, "Shift:Cmd:]")) {
switchTab(1);
return true;
}
- if (keyutil.checkKeyPressed(waveEvent, "Cmd:[")) {
+ if (keyutil.checkKeyPressed(waveEvent, "Cmd:[") || keyutil.checkKeyPressed(waveEvent, "Shift:Cmd:[")) {
switchTab(-1);
return true;
}
diff --git a/frontend/app/block/block.tsx b/frontend/app/block/block.tsx
index 8fc09edfe..15c4b364a 100644
--- a/frontend/app/block/block.tsx
+++ b/frontend/app/block/block.tsx
@@ -153,8 +153,8 @@ function getBlockHeaderText(blockIcon: string, blockData: Block, settings: Setti
return [blockIconElem, blockData.meta.title + blockIdStr];
}
}
- let viewString = blockData?.view;
- if (blockData.controller == "cmd") {
+ let viewString = blockData?.meta?.view;
+ if (blockData?.meta?.controller == "cmd") {
viewString = "cmd";
}
return [blockIconElem, viewString + blockIdStr];
@@ -259,8 +259,8 @@ const BlockFrame_Default_Component = ({
});
});
let isFocused = jotai.useAtomValue(isFocusedAtom);
- const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.view);
- const viewName = util.useAtomValueSafe(viewModel.viewName) ?? blockViewToName(blockData?.view);
+ const viewIconUnion = util.useAtomValueSafe(viewModel.viewIcon) ?? blockViewToIcon(blockData?.meta?.view);
+ const viewName = util.useAtomValueSafe(viewModel.viewName) ?? blockViewToName(blockData?.meta?.view);
const headerTextUnion = util.useAtomValueSafe(viewModel.viewText);
const preIconButton = util.useAtomValueSafe(viewModel.preIconButton);
const endIconButtons = util.useAtomValueSafe(viewModel.endIconButtons);
@@ -377,6 +377,10 @@ const BlockFrame_Default_Component = ({
headerTextElems.push(...renderHeaderElements(headerTextUnion));
}
+ function handleDoubleClick() {
+ layoutModel?.onMagnifyToggle();
+ }
+
return (
handleHeaderContextMenu(
e,
@@ -456,6 +461,9 @@ function blockViewToIcon(view: string): string {
}
function blockViewToName(view: string): string {
+ if (util.isBlank(view)) {
+ return "(No View)";
+ }
if (view == "term") {
return "Terminal";
}
@@ -476,12 +484,12 @@ function getViewElemAndModel(
blockView: string,
blockRef: React.RefObject
): { viewModel: ViewModel; viewElem: JSX.Element } {
- if (blockView == null) {
- return { viewElem: null, viewModel: null };
- }
let viewElem: JSX.Element = null;
let viewModel: ViewModel = null;
- if (blockView === "term") {
+ if (util.isBlank(blockView)) {
+ viewElem = No View;
+ viewModel = makeDefaultViewModel(blockId);
+ } else if (blockView === "term") {
const termViewModel = makeTerminalModel(blockId);
viewElem = ;
viewModel = termViewModel;
@@ -501,6 +509,7 @@ function getViewElemAndModel(
viewModel = waveAiModel;
}
if (viewModel == null) {
+ viewElem = Invalid View "{blockView}";
viewModel = makeDefaultViewModel(blockId);
}
return { viewElem, viewModel };
@@ -511,11 +520,11 @@ function makeDefaultViewModel(blockId: string): ViewModel {
let viewModel: ViewModel = {
viewIcon: jotai.atom((get) => {
const blockData = get(blockDataAtom);
- return blockViewToIcon(blockData?.view);
+ return blockViewToIcon(blockData?.meta?.view);
}),
viewName: jotai.atom((get) => {
const blockData = get(blockDataAtom);
- return blockViewToName(blockData?.view);
+ return blockViewToName(blockData?.meta?.view);
}),
viewText: jotai.atom((get) => {
const blockData = get(blockDataAtom);
@@ -532,7 +541,7 @@ const BlockPreview = React.memo(({ blockId, layoutModel }: BlockProps) => {
if (!blockData) {
return null;
}
- let { viewModel } = getViewElemAndModel(blockId, blockData?.view, null);
+ let { viewModel } = getViewElemAndModel(blockId, blockData?.meta?.view, null);
return (
{
}, []);
let { viewElem, viewModel } = React.useMemo(
- () => getViewElemAndModel(blockId, blockData?.view, blockRef),
- [blockId, blockData?.view, blockRef]
+ () => getViewElemAndModel(blockId, blockData?.meta?.view, blockRef),
+ [blockId, blockData?.meta?.view, blockRef]
);
const determineFocusedChild = React.useCallback(
diff --git a/frontend/app/view/preview.tsx b/frontend/app/view/preview.tsx
index 805d4b0ef..5467ca7a0 100644
--- a/frontend/app/view/preview.tsx
+++ b/frontend/app/view/preview.tsx
@@ -284,10 +284,10 @@ export class PreviewModel implements ViewModel {
label: "Open Terminal in New Block",
click: async () => {
const termBlockDef: BlockDef = {
- controller: "shell",
- view: "term",
meta: {
- cwd: globalStore.get(this.fileName),
+ view: "term",
+ controller: "shell",
+ "cmd:cwd": globalStore.get(this.fileName),
},
};
await createBlock(termBlockDef);
@@ -579,4 +579,4 @@ function PreviewView({ blockId, model }: { blockId: string; model: PreviewModel
);
}
-export { PreviewView, makePreviewModel };
+export { makePreviewModel, PreviewView };
diff --git a/frontend/app/view/term/term.tsx b/frontend/app/view/term/term.tsx
index ef11249d0..40f279ab7 100644
--- a/frontend/app/view/term/term.tsx
+++ b/frontend/app/view/term/term.tsx
@@ -143,7 +143,7 @@ class TermViewModel {
});
this.viewName = jotai.atom((get) => {
const blockData = get(this.blockAtom);
- if (blockData.controller == "cmd") {
+ if (blockData?.meta?.controller == "cmd") {
return "Command";
}
return "Terminal";
@@ -219,7 +219,7 @@ const TerminalView = ({ blockId, model }: TerminalViewProps) => {
}
}
const settings = globalStore.get(atoms.settingsConfigAtom);
- const termTheme = computeTheme(settings, blockData?.meta?.termtheme);
+ const termTheme = computeTheme(settings, blockData?.meta?.["term:theme"]);
const termWrap = new TermWrap(
blockId,
connectElemRef.current,
diff --git a/frontend/app/view/term/termsticker.tsx b/frontend/app/view/term/termsticker.tsx
index 1b3511e29..fb9c33f21 100644
--- a/frontend/app/view/term/termsticker.tsx
+++ b/frontend/app/view/term/termsticker.tsx
@@ -99,7 +99,7 @@ function TermSticker({ sticker, config }: { sticker: StickerType; config: Sticke
console.log("clickHandler", sticker.clickcmd, sticker.clickblockdef);
if (sticker.clickcmd) {
const b64data = btoa(sticker.clickcmd);
- WshServer.BlockInputCommand({ blockid: config.blockId, inputdata64: b64data });
+ WshServer.ControllerInputCommand({ blockid: config.blockId, inputdata64: b64data });
}
if (sticker.clickblockdef) {
createBlock(sticker.clickblockdef);
@@ -183,7 +183,7 @@ export function TermStickers({ config }: { config: StickerTermConfig }) {
imgsrc: "~/Downloads/natureicon.png",
opacity: 0.8,
pointerevents: true,
- clickblockdef: { view: "preview", meta: { file: "~/" } },
+ clickblockdef: { meta: { file: "~/", view: "preview" } },
});
stickers.push({
position: "absolute",
diff --git a/frontend/app/view/term/termtheme.ts b/frontend/app/view/term/termtheme.ts
index cdcb816d5..e39c18fc4 100644
--- a/frontend/app/view/term/termtheme.ts
+++ b/frontend/app/view/term/termtheme.ts
@@ -16,7 +16,7 @@ const TermThemeUpdater = ({ blockId, termRef }: TermThemeProps) => {
const { termthemes } = useAtomValue(atoms.settingsConfigAtom);
const [blockData] = WOS.useWaveObjectValue(WOS.makeORef("block", blockId));
let defaultThemeName = "default-dark";
- let themeName = blockData.meta?.termtheme ?? "default-dark";
+ let themeName = blockData.meta?.["term:theme"] ?? "default-dark";
const defaultTheme: TermThemeType = termthemes?.[defaultThemeName] || ({} as any);
const theme: TermThemeType = termthemes?.[themeName] || ({} as any);
diff --git a/frontend/app/view/waveai.less b/frontend/app/view/waveai.less
index 7d90eceab..4f70e77b8 100644
--- a/frontend/app/view/waveai.less
+++ b/frontend/app/view/waveai.less
@@ -24,11 +24,6 @@
.filler {
flex: 1 1 auto;
}
-
- > * {
- cursor: default;
- user-select: none;
- }
}
.chat-msg {
diff --git a/frontend/app/workspace/workspace.tsx b/frontend/app/workspace/workspace.tsx
index 38f177f7d..9b2acdae1 100644
--- a/frontend/app/workspace/workspace.tsx
+++ b/frontend/app/workspace/workspace.tsx
@@ -20,16 +20,18 @@ const Widgets = React.memo(() => {
const newWidgetModalVisible = React.useState(false);
async function clickTerminal() {
const termBlockDef: BlockDef = {
- controller: "shell",
- view: "term",
+ meta: {
+ controller: "shell",
+ view: "term",
+ },
};
createBlock(termBlockDef);
}
async function clickHome() {
const editDef: BlockDef = {
- view: "preview",
meta: {
+ view: "preview",
file: "~",
},
};
@@ -37,8 +39,8 @@ const Widgets = React.memo(() => {
}
async function clickWeb() {
const editDef: BlockDef = {
- view: "web",
meta: {
+ view: "web",
url: "https://waveterm.dev/",
},
};
diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts
index 31efef432..f280b9666 100644
--- a/frontend/types/gotypes.d.ts
+++ b/frontend/types/gotypes.d.ts
@@ -24,11 +24,8 @@ declare global {
// wstore.Block
type Block = WaveObj & {
blockdef: BlockDef;
- controller: string;
- view: string;
runtimeopts?: RuntimeOpts;
stickers?: StickerType[];
- meta: MetaType;
};
// blockcontroller.BlockControllerRuntimeStatus
@@ -40,8 +37,6 @@ declare global {
// wstore.BlockDef
type BlockDef = {
- controller?: string;
- view?: string;
files?: {[key: string]: FileDef};
meta?: MetaType;
};
@@ -60,9 +55,7 @@ declare global {
// wstore.Client
type Client = WaveObj & {
- mainwindowid: string;
windowids: string[];
- meta: MetaType;
tosagreed?: number;
};
@@ -70,7 +63,7 @@ declare global {
type CommandAppendIJsonData = {
zoneid: string;
filename: string;
- data: MetaType;
+ data: {[key: string]: any};
};
// wshrpc.CommandBlockInputData
@@ -144,7 +137,7 @@ declare global {
path?: string;
url?: string;
content?: string;
- meta?: MetaType;
+ meta?: {[key: string]: any};
};
// fileservice.FileInfo
@@ -178,10 +171,42 @@ declare global {
type LayoutNode = WaveObj & {
node?: any;
magnifiednodeid?: string;
- meta?: MetaType;
};
- type MetaType = {[key: string]: any}
+ // wstore.MetaTSType
+ type MetaType = {
+ view?: string;
+ controller?: string;
+ title?: string;
+ file?: string;
+ url?: string;
+ connection?: string;
+ icon?: string;
+ "icon:color"?: string;
+ frame?: boolean;
+ "frame:*"?: boolean;
+ "frame:bordercolor"?: string;
+ "frame:bordercolor:focused"?: string;
+ cmd?: string;
+ "cmd:*"?: boolean;
+ "cmd:interactive"?: boolean;
+ "cmd:login"?: boolean;
+ "cmd:runonstart"?: boolean;
+ "cmd:clearonstart"?: boolean;
+ "cmd:clearonrestart"?: boolean;
+ "cmd:env"?: {[key: string]: string};
+ "cmd:cwd"?: string;
+ "cmd:nowsh"?: boolean;
+ bg?: string;
+ "bg:*"?: boolean;
+ "bg:opacity"?: number;
+ "bg:blendmode"?: string;
+ "term:*"?: boolean;
+ "term:fontsize"?: number;
+ "term:fontfamily"?: string;
+ "term:mode"?: string;
+ "term:theme"?: string;
+ };
// tsgenmeta.MethodMeta
type MethodMeta = {
@@ -284,6 +309,8 @@ declare global {
autoupdate: AutoUpdateOpts;
termthemes: {[key: string]: TermThemeType};
window: WindowSettingsType;
+ defaultmeta?: MetaType;
+ presets?: {[key: string]: MetaType};
};
// wstore.StickerClickOptsType
@@ -302,7 +329,7 @@ declare global {
// wstore.StickerType
type StickerType = {
stickertype: string;
- style: MetaType;
+ style: {[key: string]: any};
clickopts?: StickerClickOptsType;
display: StickerDisplayOptsType;
};
@@ -319,7 +346,6 @@ declare global {
name: string;
layoutnode: string;
blockids: string[];
- meta: MetaType;
};
// shellexec.TermSize
@@ -392,7 +418,7 @@ declare global {
type VDomElem = {
id?: string;
tag: string;
- props?: MetaType;
+ props?: {[key: string]: any};
children?: VDomElem[];
text?: string;
};
@@ -465,7 +491,7 @@ declare global {
createdts: number;
size: number;
modts: number;
- meta: MetaType;
+ meta: {[key: string]: any};
};
// waveobj.WaveObj
@@ -473,6 +499,7 @@ declare global {
otype: string;
oid: string;
version: number;
+ meta: MetaType;
};
// wstore.WaveObjUpdate
@@ -492,7 +519,6 @@ declare global {
pos: Point;
winsize: WinSize;
lastfocusts: number;
- meta: MetaType;
};
// service.WebCallType
@@ -538,7 +564,6 @@ declare global {
type Workspace = WaveObj & {
name: string;
tabids: string[];
- meta: MetaType;
};
// wshrpc.WshRpcCommandOpts
diff --git a/pkg/blockcontroller/blockcontroller.go b/pkg/blockcontroller/blockcontroller.go
index 7ebcf752c..c15b5dd83 100644
--- a/pkg/blockcontroller/blockcontroller.go
+++ b/pkg/blockcontroller/blockcontroller.go
@@ -186,7 +186,7 @@ func (bc *BlockController) resetTerminalState() {
var shouldTruncate bool
blockData, getBlockDataErr := wstore.DBMustGet[*wstore.Block](ctx, bc.BlockId)
if getBlockDataErr == nil {
- shouldTruncate = getBoolFromMeta(blockData.Meta, wstore.MetaKey_CmdClearOnRestart, false)
+ shouldTruncate = blockData.Meta.GetBool(wstore.MetaKey_CmdClearOnRestart, false)
}
if shouldTruncate {
err := HandleTruncateBlockFile(bc.BlockId, BlockFile_Main)
@@ -208,34 +208,6 @@ func (bc *BlockController) resetTerminalState() {
}
}
-func getMetaBool(meta map[string]any, key string, def bool) bool {
- val, found := meta[key]
- if !found {
- return def
- }
- if val == nil {
- return def
- }
- if bval, ok := val.(bool); ok {
- return bval
- }
- return def
-}
-
-func getMetaStr(meta map[string]any, key string, def string) string {
- val, found := meta[key]
- if !found {
- return def
- }
- if val == nil {
- return def
- }
- if sval, ok := val.(string); ok {
- return sval
- }
- return def
-}
-
// every byte is 4-bits of randomness
func randomHexString(numHexDigits int) (string, error) {
numBytes := (numHexDigits + 1) / 2 // Calculate the number of bytes needed
@@ -248,7 +220,7 @@ func randomHexString(numHexDigits int) (string, error) {
return hexStr[:numHexDigits], nil // Return the exact number of hex digits
}
-func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[string]any) error {
+func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj.MetaMapType) error {
// create a circular blockfile for the output
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
defer cancelFn()
@@ -276,7 +248,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
return shellProcErr
}
var remoteDomainSocketName string
- remoteName := getMetaStr(blockMeta, "connection", "")
+ remoteName := blockMeta.GetString(wstore.MetaKey_Connection, "")
isRemote := remoteName != ""
if isRemote {
randStr, err := randomHexString(16) // 64-bits of randomness
@@ -289,7 +261,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
cmdOpts := shellexec.CommandOptsType{
Env: make(map[string]string),
}
- if !getMetaBool(blockMeta, "nowsh", false) {
+ if !blockMeta.GetBool(wstore.MetaKey_CmdNoWsh, false) {
if isRemote {
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, remoteDomainSocketName)
if err != nil {
@@ -307,44 +279,31 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta map[str
if bc.ControllerType == BlockController_Shell {
cmdOpts.Interactive = true
cmdOpts.Login = true
- cmdOpts.Cwd, _ = blockMeta["cmd:cwd"].(string)
+ cmdOpts.Cwd = blockMeta.GetString(wstore.MetaKey_CmdCwd, "")
if cmdOpts.Cwd != "" {
cmdOpts.Cwd = wavebase.ExpandHomeDir(cmdOpts.Cwd)
}
} else if bc.ControllerType == BlockController_Cmd {
- if _, ok := blockMeta["cmd"].(string); ok {
- cmdStr = blockMeta["cmd"].(string)
- } else {
+ cmdStr = blockMeta.GetString(wstore.MetaKey_Cmd, "")
+ if cmdStr == "" {
return fmt.Errorf("missing cmd in block meta")
}
- if _, ok := blockMeta["cmd:cwd"].(string); ok {
- cmdOpts.Cwd = blockMeta["cmd:cwd"].(string)
- if cmdOpts.Cwd != "" {
- cmdOpts.Cwd = wavebase.ExpandHomeDir(cmdOpts.Cwd)
- }
+ cmdOpts.Cwd = blockMeta.GetString(wstore.MetaKey_CmdCwd, "")
+ if cmdOpts.Cwd != "" {
+ cmdOpts.Cwd = wavebase.ExpandHomeDir(cmdOpts.Cwd)
}
- if _, ok := blockMeta["cmd:interactive"]; ok {
- if blockMeta["cmd:interactive"].(bool) {
- cmdOpts.Interactive = true
+ cmdOpts.Interactive = blockMeta.GetBool(wstore.MetaKey_CmdInteractive, false)
+ cmdOpts.Login = blockMeta.GetBool(wstore.MetaKey_CmdLogin, false)
+ cmdEnv := blockMeta.GetMap(wstore.MetaKey_CmdEnv)
+ for k, v := range cmdEnv {
+ if v == nil {
+ continue
}
- }
- if _, ok := blockMeta["cmd:login"]; ok {
- if blockMeta["cmd:login"].(bool) {
- cmdOpts.Login = true
+ if _, ok := v.(string); ok {
+ cmdOpts.Env[k] = v.(string)
}
- }
- if _, ok := blockMeta["cmd:env"].(map[string]any); ok {
- cmdEnv := blockMeta["cmd:env"].(map[string]any)
- for k, v := range cmdEnv {
- if v == nil {
- continue
- }
- if _, ok := v.(string); ok {
- cmdOpts.Env[k] = v.(string)
- }
- if _, ok := v.(float64); ok {
- cmdOpts.Env[k] = fmt.Sprintf("%v", v)
- }
+ if _, ok := v.(float64); ok {
+ cmdOpts.Env[k] = fmt.Sprintf("%v", v)
}
}
} else {
@@ -477,8 +436,9 @@ func (bc *BlockController) run(bdata *wstore.Block, blockMeta map[string]any) {
bc.Status = Status_Running
return true
})
- if bdata.Controller != BlockController_Shell && bdata.Controller != BlockController_Cmd {
- log.Printf("unknown controller %q\n", bdata.Controller)
+ controllerName := bdata.Meta.GetString(wstore.MetaKey_Controller, "")
+ if controllerName != BlockController_Shell && controllerName != BlockController_Cmd {
+ log.Printf("unknown controller %q\n", controllerName)
return
}
if getBoolFromMeta(blockMeta, wstore.MetaKey_CmdClearOnStart, false) {
@@ -527,12 +487,13 @@ func StartBlockController(ctx context.Context, tabId string, blockId string) err
if err != nil {
return fmt.Errorf("error getting block: %w", err)
}
- if blockData.Controller == "" {
+ controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "")
+ if controllerName == "" {
// nothing to start
return nil
}
- if blockData.Controller != BlockController_Shell && blockData.Controller != BlockController_Cmd {
- return fmt.Errorf("unknown controller %q", blockData.Controller)
+ if controllerName != BlockController_Shell && controllerName != BlockController_Cmd {
+ return fmt.Errorf("unknown controller %q", controllerName)
}
globalLock.Lock()
defer globalLock.Unlock()
@@ -542,7 +503,7 @@ func StartBlockController(ctx context.Context, tabId string, blockId string) err
}
bc := &BlockController{
Lock: &sync.Mutex{},
- ControllerType: blockData.Controller,
+ ControllerType: controllerName,
TabId: tabId,
BlockId: blockId,
Status: Status_Init,
diff --git a/pkg/ijson/ijson.go b/pkg/ijson/ijson.go
index bb442f3ad..1a21393bb 100644
--- a/pkg/ijson/ijson.go
+++ b/pkg/ijson/ijson.go
@@ -10,6 +10,7 @@ import (
"fmt"
"regexp"
"strconv"
+ "strings"
)
// ijson values are built out of standard go building blocks:
@@ -56,6 +57,45 @@ func MakeAppendCommand(path Path, value any) Command {
}
}
+var pathPartKeyRe = regexp.MustCompile(`^[a-zA-Z0-9:_#-]+`)
+
+func ParseSimplePath(input string) ([]any, error) {
+ var path []any
+ // Scan the input string character by character
+ for i := 0; i < len(input); {
+ if input[i] == '[' {
+ // Handle the index
+ end := strings.Index(input[i:], "]")
+ if end == -1 {
+ return nil, fmt.Errorf("unmatched bracket at position %d", i)
+ }
+ index, err := strconv.Atoi(input[i+1 : i+end])
+ if err != nil {
+ return nil, fmt.Errorf("invalid index at position %d: %v", i, err)
+ }
+ path = append(path, index)
+ i += end + 1
+ } else {
+ // Handle the key
+ j := i
+ for j < len(input) && input[j] != '.' && input[j] != '[' {
+ j++
+ }
+ key := input[i:j]
+ if !pathPartKeyRe.MatchString(key) {
+ return nil, fmt.Errorf("invalid key at position %d: %s", i, key)
+ }
+ path = append(path, key)
+ i = j
+ }
+ if i < len(input) && input[i] == '.' {
+ i++
+ }
+ }
+
+ return path, nil
+}
+
type PathError struct {
Err string
}
diff --git a/pkg/service/blockservice/blockservice.go b/pkg/service/blockservice/blockservice.go
index bd3f5506d..73b0d896d 100644
--- a/pkg/service/blockservice/blockservice.go
+++ b/pkg/service/blockservice/blockservice.go
@@ -66,8 +66,9 @@ func (bs *BlockService) SaveWaveAiData(ctx context.Context, blockId string, hist
if err != nil {
return err
}
- if block.View != "waveai" {
- return fmt.Errorf("invalid view type: %s", block.View)
+ viewName := block.Meta.GetString(wstore.MetaKey_View, "")
+ if viewName != "waveai" {
+ return fmt.Errorf("invalid view type: %s", viewName)
}
historyBytes, err := json.Marshal(history)
if err != nil {
diff --git a/pkg/service/objectservice/objectservice.go b/pkg/service/objectservice/objectservice.go
index 276a02e78..31a30df64 100644
--- a/pkg/service/objectservice/objectservice.go
+++ b/pkg/service/objectservice/objectservice.go
@@ -189,7 +189,8 @@ func (svc *ObjectService) CreateBlock(uiContext wstore.UIContext, blockDef *wsto
if err != nil {
return "", nil, fmt.Errorf("error creating block: %w", err)
}
- if blockData.Controller != "" {
+ controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "")
+ if controllerName != "" {
err = blockcontroller.StartBlockController(ctx, uiContext.ActiveTabId, blockData.OID)
if err != nil {
return "", nil, fmt.Errorf("error starting block controller: %w", err)
@@ -228,7 +229,7 @@ func (svc *ObjectService) UpdateObjectMeta_Meta() tsgenmeta.MethodMeta {
}
}
-func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr string, meta map[string]any) (wstore.UpdatesRtnType, error) {
+func (svc *ObjectService) UpdateObjectMeta(uiContext wstore.UIContext, orefStr string, meta wstore.MetaMapType) (wstore.UpdatesRtnType, error) {
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
defer cancelFn()
ctx = wstore.ContextWithUpdates(ctx)
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 493f93971..566faa30d 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -89,7 +89,7 @@ func convertNumber(argType reflect.Type, jsonArg float64) (any, error) {
func convertComplex(argType reflect.Type, jsonArg any) (any, error) {
nativeArgVal := reflect.New(argType)
- err := utilfn.DoMapStucture(nativeArgVal.Interface(), jsonArg)
+ err := utilfn.DoMapStructure(nativeArgVal.Interface(), jsonArg)
if err != nil {
return nil, err
}
diff --git a/pkg/tsgen/tsgen.go b/pkg/tsgen/tsgen.go
index 6f45cc930..fc1ff0b60 100644
--- a/pkg/tsgen/tsgen.go
+++ b/pkg/tsgen/tsgen.go
@@ -45,6 +45,7 @@ var ExtraTypes = []any{
vdom.Elem{},
vdom.VDomFuncType{},
vdom.VDomRefType{},
+ wstore.MetaTSType{},
}
// add extra type unions to generate here
@@ -55,7 +56,7 @@ var TypeUnions = []tsgenmeta.TypeUnionMeta{
var contextRType = reflect.TypeOf((*context.Context)(nil)).Elem()
var errorRType = reflect.TypeOf((*error)(nil)).Elem()
var anyRType = reflect.TypeOf((*interface{})(nil)).Elem()
-var metaRType = reflect.TypeOf((*map[string]any)(nil)).Elem()
+var metaRType = reflect.TypeOf((*wstore.MetaMapType)(nil)).Elem()
var uiContextRType = reflect.TypeOf((*wstore.UIContext)(nil)).Elem()
var waveObjRType = reflect.TypeOf((*waveobj.WaveObj)(nil)).Elem()
var updatesRtnRType = reflect.TypeOf(wstore.UpdatesRtnType{})
@@ -86,6 +87,9 @@ func getTSFieldName(field reflect.StructField) string {
if namePart == "-" {
return ""
}
+ if strings.Contains(namePart, ":") {
+ return "\"" + namePart + "\""
+ }
return namePart
}
// if namePart is empty, still uses default
@@ -155,8 +159,9 @@ func TypeToTSType(t reflect.Type, tsTypesMap map[reflect.Type]string) (string, [
}
var tsRenameMap = map[string]string{
- "Window": "WaveWindow",
- "Elem": "VDomElem",
+ "Window": "WaveWindow",
+ "Elem": "VDomElem",
+ "MetaTSType": "MetaType",
}
func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]string) (string, []reflect.Type) {
@@ -183,7 +188,7 @@ func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]stri
if fieldName == "" {
continue
}
- if isWaveObj && (fieldName == waveobj.OTypeKeyName || fieldName == waveobj.OIDKeyName || fieldName == waveobj.VersionKeyName) {
+ if isWaveObj && (fieldName == waveobj.OTypeKeyName || fieldName == waveobj.OIDKeyName || fieldName == waveobj.VersionKeyName || fieldName == waveobj.MetaKeyName) {
continue
}
optMarker := ""
@@ -192,6 +197,9 @@ func generateTSTypeInternal(rtype reflect.Type, tsTypesMap map[reflect.Type]stri
}
tsTypeTag := field.Tag.Get("tstype")
if tsTypeTag != "" {
+ if tsTypeTag == "-" {
+ continue
+ }
buf.WriteString(fmt.Sprintf(" %s%s: %s;\n", fieldName, optMarker, tsTypeTag))
continue
}
@@ -216,14 +224,11 @@ func GenerateWaveObjTSType() string {
buf.WriteString(" otype: string;\n")
buf.WriteString(" oid: string;\n")
buf.WriteString(" version: number;\n")
+ buf.WriteString(" meta: MetaType;\n")
buf.WriteString("};\n")
return buf.String()
}
-func GenerateMetaType() string {
- return "type MetaType = {[key: string]: any}\n"
-}
-
func GenerateTSTypeUnion(unionMeta tsgenmeta.TypeUnionMeta, tsTypeMap map[reflect.Type]string) {
rtn := generateTSTypeUnionInternal(unionMeta)
tsTypeMap[unionMeta.BaseType] = rtn
@@ -257,10 +262,6 @@ func GenerateTSType(rtype reflect.Type, tsTypesMap map[reflect.Type]string) {
if rtype.Kind() == reflect.Chan {
rtype = rtype.Elem()
}
- if rtype == metaRType {
- tsTypesMap[metaRType] = GenerateMetaType()
- return
- }
if rtype == contextRType || rtype == errorRType || rtype == anyRType {
return
}
diff --git a/pkg/util/utilfn/utilfn.go b/pkg/util/utilfn/utilfn.go
index b3067f51b..7f1aaa9e9 100644
--- a/pkg/util/utilfn/utilfn.go
+++ b/pkg/util/utilfn/utilfn.go
@@ -747,7 +747,7 @@ func ReUnmarshal(out any, in any) error {
}
// does a mapstructure using "json" tags
-func DoMapStucture(out any, input any) error {
+func DoMapStructure(out any, input any) error {
dconfig := &mapstructure.DecoderConfig{
Result: out,
TagName: "json",
@@ -826,3 +826,14 @@ func StarMatchString(pattern string, s string, delimiter string) bool {
// Check if both pattern and string are fully matched
return pLen == sLen
}
+
+func MergeStrMaps[T any](m1 map[string]T, m2 map[string]T) map[string]T {
+ rtn := make(map[string]T)
+ for key, val := range m1 {
+ rtn[key] = val
+ }
+ for key, val := range m2 {
+ rtn[key] = val
+ }
+ return rtn
+}
diff --git a/pkg/waveobj/metamap.go b/pkg/waveobj/metamap.go
new file mode 100644
index 000000000..57a881ea5
--- /dev/null
+++ b/pkg/waveobj/metamap.go
@@ -0,0 +1,74 @@
+// Copyright 2024, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package waveobj
+
+type MetaMapType map[string]any
+
+func (m MetaMapType) GetString(key string, def string) string {
+ if v, ok := m[key]; ok {
+ if s, ok := v.(string); ok {
+ return s
+ }
+ }
+ return def
+}
+
+func (m MetaMapType) GetBool(key string, def bool) bool {
+ if v, ok := m[key]; ok {
+ if b, ok := v.(bool); ok {
+ return b
+ }
+ }
+ return def
+}
+
+func (m MetaMapType) GetInt(key string, def int) int {
+ if v, ok := m[key]; ok {
+ if fval, ok := v.(float64); ok {
+ return int(fval)
+ }
+ }
+ return def
+}
+
+func (m MetaMapType) GetFloat(key string, def float64) float64 {
+ if v, ok := m[key]; ok {
+ if fval, ok := v.(float64); ok {
+ return fval
+ }
+ }
+ return def
+}
+
+func (m MetaMapType) GetMap(key string) MetaMapType {
+ if v, ok := m[key]; ok {
+ if mval, ok := v.(map[string]any); ok {
+ return MetaMapType(mval)
+ }
+ }
+ return nil
+}
+
+func (m MetaMapType) GetArray(key string) []any {
+ if v, ok := m[key]; ok {
+ if aval, ok := v.([]any); ok {
+ return aval
+ }
+ }
+ return nil
+}
+
+func (m MetaMapType) GetStringArray(key string) []string {
+ arr := m.GetArray(key)
+ if len(arr) == 0 {
+ return nil
+ }
+ rtn := make([]string, 0, len(arr))
+ for _, v := range arr {
+ if s, ok := v.(string); ok {
+ rtn = append(rtn, s)
+ }
+ }
+ return rtn
+}
diff --git a/pkg/waveobj/waveobj.go b/pkg/waveobj/waveobj.go
index 799c35710..16a8b8852 100644
--- a/pkg/waveobj/waveobj.go
+++ b/pkg/waveobj/waveobj.go
@@ -106,6 +106,7 @@ type waveObjDesc struct {
var waveObjMap = sync.Map{}
var waveObjRType = reflect.TypeOf((*WaveObj)(nil)).Elem()
+var metaMapRType = reflect.TypeOf(MetaMapType{})
func RegisterType(rtype reflect.Type) {
if rtype.Kind() != reflect.Ptr {
@@ -143,10 +144,8 @@ func RegisterType(rtype reflect.Type) {
if !found {
panic(fmt.Sprintf("missing Meta field for %v", rtype))
}
- if metaField.Type.Kind() != reflect.Map ||
- metaField.Type.Elem().Kind() != reflect.Interface ||
- metaField.Type.Key().Kind() != reflect.String {
- panic(fmt.Sprintf("Meta field must be map[string]any for %v", rtype))
+ if metaField.Type != metaMapRType {
+ panic(fmt.Sprintf("Meta field must be MetaMapType for %v", rtype))
}
_, found = waveObjMap.Load(otype)
if found {
@@ -200,12 +199,16 @@ func SetVersion(waveObj WaveObj, version int) {
reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.VersionField.Index).SetInt(int64(version))
}
-func GetMeta(waveObj WaveObj) map[string]any {
+func GetMeta(waveObj WaveObj) MetaMapType {
desc := getWaveObjDesc(waveObj.GetOType())
if desc == nil {
return nil
}
- return reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.MetaField.Index).Interface().(map[string]any)
+ mval := reflect.ValueOf(waveObj).Elem().FieldByIndex(desc.MetaField.Index).Interface()
+ if mval == nil {
+ return nil
+ }
+ return mval.(MetaMapType)
}
func SetMeta(waveObj WaveObj, meta map[string]any) {
diff --git a/pkg/wconfig/settingsconfig.go b/pkg/wconfig/settingsconfig.go
index 9c7d3fb17..06a90502d 100644
--- a/pkg/wconfig/settingsconfig.go
+++ b/pkg/wconfig/settingsconfig.go
@@ -8,6 +8,7 @@ import (
"path/filepath"
"github.com/wavetermdev/thenextwave/pkg/wavebase"
+ "github.com/wavetermdev/thenextwave/pkg/waveobj"
"github.com/wavetermdev/thenextwave/pkg/wstore"
)
@@ -98,6 +99,9 @@ type SettingsConfigType struct {
AutoUpdate *AutoUpdateOpts `json:"autoupdate"`
TermThemes TermThemesConfigType `json:"termthemes"`
WindowSettings WindowSettingsType `json:"window"`
+
+ DefaultMeta *waveobj.MetaMapType `json:"defaultmeta,omitempty"`
+ Presets map[string]*waveobj.MetaMapType `json:"presets,omitempty"`
}
var DefaultTermDarkTheme = TermThemeType{
@@ -181,30 +185,38 @@ func applyDefaultSettings(settings *SettingsConfigType) {
Icon: "files",
Label: "files",
BlockDef: wstore.BlockDef{
- View: "preview",
- Meta: map[string]any{"file": wavebase.GetHomeDir()},
+ Meta: map[string]any{
+ wstore.MetaKey_View: "preview",
+ wstore.MetaKey_File: wavebase.GetHomeDir(),
+ },
},
},
{
Icon: "chart-simple",
Label: "chart",
BlockDef: wstore.BlockDef{
- View: "plot",
+ Meta: map[string]any{
+ wstore.MetaKey_View: "plot",
+ },
},
},
{
Icon: "globe",
Label: "web",
BlockDef: wstore.BlockDef{
- View: "web",
- Meta: map[string]any{"url": "https://waveterm.dev/"},
+ Meta: map[string]any{
+ wstore.MetaKey_View: "web",
+ wstore.MetaKey_Url: "https://waveterm.dev/",
+ },
},
},
{
Icon: "sparkles",
Label: "waveai",
BlockDef: wstore.BlockDef{
- View: "waveai",
+ Meta: map[string]any{
+ wstore.MetaKey_View: "waveai",
+ },
},
},
}
diff --git a/pkg/web/webcmd/webcmd.go b/pkg/web/webcmd/webcmd.go
index 78980f616..7208591db 100644
--- a/pkg/web/webcmd/webcmd.go
+++ b/pkg/web/webcmd/webcmd.go
@@ -72,21 +72,21 @@ func ParseWSCommandMap(cmdMap map[string]any) (WSCommandType, error) {
switch cmdType {
case WSCommand_SetBlockTermSize:
var cmd SetBlockTermSizeWSCommand
- err := utilfn.DoMapStucture(&cmd, cmdMap)
+ err := utilfn.DoMapStructure(&cmd, cmdMap)
if err != nil {
return nil, fmt.Errorf("error decoding SetBlockTermSizeWSCommand: %w", err)
}
return &cmd, nil
case WSCommand_BlockInput:
var cmd BlockInputWSCommand
- err := utilfn.DoMapStucture(&cmd, cmdMap)
+ err := utilfn.DoMapStructure(&cmd, cmdMap)
if err != nil {
return nil, fmt.Errorf("error decoding BlockInputWSCommand: %w", err)
}
return &cmd, nil
case WSCommand_Rpc:
var cmd WSRpcCommand
- err := utilfn.DoMapStucture(&cmd, cmdMap)
+ err := utilfn.DoMapStructure(&cmd, cmdMap)
if err != nil {
return nil, fmt.Errorf("error decoding WSRpcCommand: %w", err)
}
diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go
index e9cc46f62..4d06eab15 100644
--- a/pkg/wshrpc/wshclient/wshclient.go
+++ b/pkg/wshrpc/wshclient/wshclient.go
@@ -96,8 +96,8 @@ func FileWriteCommand(w *wshutil.WshRpc, data wshrpc.CommandFileData, opts *wshr
}
// command "getmeta", wshserver.GetMetaCommand
-func GetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandGetMetaData, opts *wshrpc.WshRpcCommandOpts) (map[string]interface {}, error) {
- resp, err := sendRpcRequestCallHelper[map[string]interface {}](w, "getmeta", data, opts)
+func GetMetaCommand(w *wshutil.WshRpc, data wshrpc.CommandGetMetaData, opts *wshrpc.WshRpcCommandOpts) (waveobj.MetaMapType, error) {
+ resp, err := sendRpcRequestCallHelper[waveobj.MetaMapType](w, "getmeta", data, opts)
return resp, err
}
diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go
index 627ee557a..d45da0775 100644
--- a/pkg/wshrpc/wshrpctypes.go
+++ b/pkg/wshrpc/wshrpctypes.go
@@ -45,8 +45,6 @@ const (
Command_StreamWaveAi = "streamwaveai"
)
-type MetaDataType = map[string]any
-
type RespOrErrorUnion[T any] struct {
Response T
Error error
@@ -55,7 +53,7 @@ type RespOrErrorUnion[T any] struct {
type WshRpcInterface interface {
AuthenticateCommand(ctx context.Context, data string) error
MessageCommand(ctx context.Context, data CommandMessageData) error
- GetMetaCommand(ctx context.Context, data CommandGetMetaData) (MetaDataType, error)
+ GetMetaCommand(ctx context.Context, data CommandGetMetaData) (wstore.MetaMapType, error)
SetMetaCommand(ctx context.Context, data CommandSetMetaData) error
SetViewCommand(ctx context.Context, data CommandBlockSetViewData) error
ControllerInputCommand(ctx context.Context, data CommandBlockInputData) error
@@ -130,8 +128,8 @@ type CommandGetMetaData struct {
}
type CommandSetMetaData struct {
- ORef waveobj.ORef `json:"oref" wshcontext:"BlockORef"`
- Meta MetaDataType `json:"meta"`
+ ORef waveobj.ORef `json:"oref" wshcontext:"BlockORef"`
+ Meta wstore.MetaMapType `json:"meta"`
}
type CommandResolveIdsData struct {
diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go
index 0b9f22124..44876a530 100644
--- a/pkg/wshrpc/wshserver/wshserver.go
+++ b/pkg/wshrpc/wshserver/wshserver.go
@@ -67,7 +67,7 @@ func (ws *WshServer) StreamWaveAiCommand(ctx context.Context, request wshrpc.Ope
return waveai.RunLocalCompletionStream(ctx, request)
}
-func (ws *WshServer) GetMetaCommand(ctx context.Context, data wshrpc.CommandGetMetaData) (wshrpc.MetaDataType, error) {
+func (ws *WshServer) GetMetaCommand(ctx context.Context, data wshrpc.CommandGetMetaData) (waveobj.MetaMapType, error) {
log.Printf("calling meta: %s\n", data.ORef)
obj, err := wstore.DBGetORef(ctx, data.ORef)
if err != nil {
@@ -82,31 +82,9 @@ func (ws *WshServer) GetMetaCommand(ctx context.Context, data wshrpc.CommandGetM
func (ws *WshServer) SetMetaCommand(ctx context.Context, data wshrpc.CommandSetMetaData) error {
log.Printf("SETMETA: %s | %v\n", data.ORef, data.Meta)
oref := data.ORef
- if oref.IsEmpty() {
- return fmt.Errorf("no oref")
- }
- obj, err := wstore.DBGetORef(ctx, oref)
+ err := wstore.UpdateObjectMeta(ctx, oref, data.Meta)
if err != nil {
- return fmt.Errorf("error getting object: %w", err)
- }
- if obj == nil {
- return nil
- }
- meta := waveobj.GetMeta(obj)
- if meta == nil {
- meta = make(map[string]any)
- }
- for k, v := range data.Meta {
- if v == nil {
- delete(meta, k)
- continue
- }
- meta[k] = v
- }
- waveobj.SetMeta(obj, meta)
- err = wstore.DBUpdate(ctx, obj)
- if err != nil {
- return fmt.Errorf("error updating block: %w", err)
+ return fmt.Errorf("error updating object meta: %w", err)
}
sendWaveObjUpdate(oref)
return nil
@@ -177,7 +155,8 @@ func (ws *WshServer) CreateBlockCommand(ctx context.Context, data wshrpc.Command
if err != nil {
return nil, fmt.Errorf("error creating block: %w", err)
}
- if blockData.Controller != "" {
+ controllerName := blockData.Meta.GetString(wstore.MetaKey_Controller, "")
+ if controllerName != "" {
// TODO
err = blockcontroller.StartBlockController(ctx, data.TabId, blockData.OID)
if err != nil {
@@ -211,7 +190,7 @@ func (ws *WshServer) SetViewCommand(ctx context.Context, data wshrpc.CommandBloc
if err != nil {
return fmt.Errorf("error getting block: %w", err)
}
- block.View = data.View
+ block.Meta[wstore.MetaKey_View] = data.View
err = wstore.DBUpdate(ctx, block)
if err != nil {
return fmt.Errorf("error updating block: %w", err)
diff --git a/pkg/wstore/wstore.go b/pkg/wstore/wstore.go
index 0efc14417..3988fa7bd 100644
--- a/pkg/wstore/wstore.go
+++ b/pkg/wstore/wstore.go
@@ -236,8 +236,6 @@ func CreateBlock(ctx context.Context, tabId string, blockDef *BlockDef, rtOpts *
blockData := &Block{
OID: blockId,
BlockDef: blockDef,
- Controller: blockDef.Controller,
- View: blockDef.View,
RuntimeOpts: rtOpts,
Meta: blockDef.Meta,
}
@@ -299,36 +297,21 @@ func DeleteTab(ctx context.Context, workspaceId string, tabId string) error {
})
}
-func UpdateMeta(ctx context.Context, oref waveobj.ORef, meta map[string]any) error {
+func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta MetaMapType) error {
return WithTx(ctx, func(tx *TxWrap) error {
- obj, _ := DBGetORef(tx.Context(), oref)
- if obj == nil {
- return fmt.Errorf("object not found: %q", oref)
+ if oref.IsEmpty() {
+ return fmt.Errorf("empty object reference")
}
- // obj.SetMeta(meta)
- DBUpdate(tx.Context(), obj)
- return nil
- })
-}
-
-func UpdateObjectMeta(ctx context.Context, oref waveobj.ORef, meta map[string]any) error {
- return WithTx(ctx, func(tx *TxWrap) error {
obj, _ := DBGetORef(tx.Context(), oref)
if obj == nil {
- return fmt.Errorf("object not found: %q", oref)
+ return ErrNotFound
}
objMeta := waveobj.GetMeta(obj)
if objMeta == nil {
objMeta = make(map[string]any)
}
- for k, v := range meta {
- if v == nil {
- delete(objMeta, k)
- continue
- }
- objMeta[k] = v
- }
- waveobj.SetMeta(obj, objMeta)
+ newMeta := MergeMeta(objMeta, meta)
+ waveobj.SetMeta(obj, newMeta)
DBUpdate(tx.Context(), obj)
return nil
})
@@ -441,19 +424,6 @@ func EnsureInitialData() error {
return fmt.Errorf("error creating client: %w", err)
}
}
- if client.MainWindowId != "" {
- // convert to windowIds
- client.WindowIds = []string{client.MainWindowId}
- client.MainWindowId = ""
- err = DBUpdate(ctx, client)
- if err != nil {
- return fmt.Errorf("error updating client: %w", err)
- }
- client, err = DBGetSingleton[*Client](ctx)
- if err != nil {
- return fmt.Errorf("error getting client (after main window update): %w", err)
- }
- }
if len(client.WindowIds) > 0 {
return nil
}
diff --git a/pkg/wstore/wstore_meta.go b/pkg/wstore/wstore_meta.go
new file mode 100644
index 000000000..258050f9f
--- /dev/null
+++ b/pkg/wstore/wstore_meta.go
@@ -0,0 +1,153 @@
+// Copyright 2024, Command Line Inc.
+// SPDX-License-Identifier: Apache-2.0
+
+package wstore
+
+import (
+ "strings"
+
+ "github.com/wavetermdev/thenextwave/pkg/waveobj"
+)
+
+const Entity_Any = "any"
+
+type MetaMapType = waveobj.MetaMapType
+
+// well known meta keys
+// to add a new key, add it here and add it to MetaTSType (make sure the keys match)
+// TODO: will code generate one side of this so we don't need to add the keys in two places
+// will probably drive this off the meta decls so we can add more information and validate the keys/values
+const (
+ MetaKey_View = "view"
+ MetaKey_Controller = "controller"
+ MetaKey_Title = "title"
+ MetaKey_File = "file"
+ MetaKey_Url = "url"
+ MetaKey_Connection = "connection"
+
+ MetaKey_Icon = "icon"
+ MetaKey_IconColor = "icon:color"
+
+ MetaKey_Frame = "frame"
+ MetaKey_FrameBorderColor = "frame:bordercolor"
+ MetaKey_FrameBorderColor_Focused = "frame:bordercolor:focused"
+
+ MetaKey_Cmd = "cmd"
+ MetaKey_CmdInteractive = "cmd:interactive"
+ MetaKey_CmdLogin = "cmd:login"
+ MetaKey_CmdRunOnStart = "cmd:runonstart"
+ MetaKey_CmdClearOnStart = "cmd:clearonstart"
+ MetaKey_CmdClearOnRestart = "cmd:clearonrestart"
+ MetaKey_CmdEnv = "cmd:env"
+ MetaKey_CmdCwd = "cmd:cwd"
+ MetaKey_CmdNoWsh = "cmd:nowsh"
+
+ MetaKey_Bg = "bg"
+ MetaKey_BgOpacity = "bg:opacity"
+ MetaKey_BgBlendMode = "bg:blendmode"
+
+ MetaKey_TermFontSize = "term:fontsize"
+ MetaKey_TermFontFamily = "term:fontfamily"
+ MetaKey_TermMode = "term:mode"
+ MetaKey_TermTheme = "term:theme"
+)
+
+// for typescript typing
+type MetaTSType struct {
+ // shared
+ View string `json:"view,omitempty"`
+ Controller string `json:"controller,omitempty"`
+ Title string `json:"title,omitempty"`
+ File string `json:"file,omitempty"`
+ Url string `json:"url,omitempty"`
+ Connection string `json:"connection,omitempty"`
+
+ Icon string `json:"icon,omitempty"`
+ IconColor string `json:"icon:color,omitempty"`
+
+ Frame bool `json:"frame,omitempty"`
+ FrameClear bool `json:"frame:*,omitempty"`
+ FrameBorderColor string `json:"frame:bordercolor,omitempty"`
+ FrameBorderColor_Focused string `json:"frame:bordercolor:focused,omitempty"`
+
+ Cmd string `json:"cmd,omitempty"`
+ CmdClear bool `json:"cmd:*,omitempty"`
+ CmdInteractive bool `json:"cmd:interactive,omitempty"`
+ CmdLogin bool `json:"cmd:login,omitempty"`
+ CmdRunOnStart bool `json:"cmd:runonstart,omitempty"`
+ CmdClearOnStart bool `json:"cmd:clearonstart,omitempty"`
+ CmdClearOnRestart bool `json:"cmd:clearonrestart,omitempty"`
+ CmdEnv map[string]string `json:"cmd:env,omitempty"`
+ CmdCwd string `json:"cmd:cwd,omitempty"`
+ CmdNoWsh bool `json:"cmd:nowsh,omitempty"`
+
+ // for tabs
+ Bg string `json:"bg,omitempty"`
+ BgClear bool `json:"bg:*,omitempty"`
+ BgOpacity float64 `json:"bg:opacity,omitempty"`
+ BgBlendMode string `json:"bg:blendmode,omitempty"`
+
+ TermClear bool `json:"term:*,omitempty"`
+ TermFontSize int `json:"term:fontsize,omitempty"`
+ TermFontFamily string `json:"term:fontfamily,omitempty"`
+ TermMode string `json:"term:mode,omitempty"`
+ TermTheme string `json:"term:theme,omitempty"`
+}
+
+type MetaDataDecl struct {
+ Key string `json:"key"`
+ Desc string `json:"desc,omitempty"`
+ Type string `json:"type"` // string, int, float, bool, array, object
+ Default any `json:"default,omitempty"`
+ StrOptions []string `json:"stroptions,omitempty"`
+ NumRange []*int `json:"numrange,omitempty"` // inclusive, null means no limit
+ Entity []string `json:"entity"` // what entities this applies to, e.g. "block", "tab", "any", etc.
+ Special []string `json:"special,omitempty"` // special handling. things that need to happen if this gets updated
+}
+
+type MetaPresetDecl struct {
+ Preset string `json:"preset"`
+ Desc string `json:"desc,omitempty"`
+ Keys []string `json:"keys"`
+ Entity []string `json:"entity"` // what entities this applies to, e.g. "block", "tab", etc.
+}
+
+// returns a clean copy of meta with mergeMeta merged in
+func MergeMeta(meta MetaMapType, metaUpdate MetaMapType) MetaMapType {
+ rtn := make(MetaMapType)
+ for k, v := range meta {
+ rtn[k] = v
+ }
+ // deal with "section:*" keys
+ for k := range metaUpdate {
+ if !strings.HasSuffix(k, ":*") {
+ continue
+ }
+ if !metaUpdate.GetBool(k, false) {
+ continue
+ }
+ prefix := strings.TrimSuffix(k, ":*")
+ if prefix == "" {
+ continue
+ }
+ // delete "[prefix]" and all keys that start with "[prefix]:"
+ prefixColon := prefix + ":"
+ for k2 := range rtn {
+ if k2 == prefix || strings.HasPrefix(k2, prefixColon) {
+ delete(rtn, k2)
+ }
+ }
+ }
+ // now deal with regular keys
+ for k, v := range metaUpdate {
+ if strings.HasSuffix(k, ":*") {
+ continue
+ }
+ if v == nil {
+ delete(rtn, k)
+ continue
+ }
+ rtn[k] = v
+ }
+ return rtn
+}
diff --git a/pkg/wstore/wstore_types.go b/pkg/wstore/wstore_types.go
index b9d59befb..99e1c7c11 100644
--- a/pkg/wstore/wstore_types.go
+++ b/pkg/wstore/wstore_types.go
@@ -12,54 +12,6 @@ import (
"github.com/wavetermdev/thenextwave/pkg/waveobj"
)
-// well known meta keys
-const (
- MetaKey_Title = "title"
- MetaKey_File = "file"
- MetaKey_Url = "url"
- MetaKey_Icon = "icon"
- MetaKey_IconColor = "icon:color"
- MetaKey_Frame = "frame"
- MetaKey_FrameBorderColor = "frame:bordercolor"
- MetaKey_FrameBorderColor_Focused = "frame:bordercolor:focused"
- MetaKey_Cmd = "cmd"
- MetaKey_CmdInteractive = "cmd:interactive"
- MetaKey_CmdLogin = "cmd:login"
- MetaKey_CmdRunOnStart = "cmd:runonstart"
- MetaKey_CmdClearOnStart = "cmd:clearonstart"
- MetaKey_CmdClearOnRestart = "cmd:clearonrestart"
- MetaKey_CmdEnv = "cmd:env"
- MetaKey_CmdCwd = "cmd:cwd"
-)
-
-type MetaType struct {
- View string `json:"view,omitempty"`
- Controller string `json:"controller,omitempty"`
- Title string `json:"title,omitempty"`
- File string `json:"file,omitempty"`
- Url string `json:"url,omitempty"`
-
- Icon string `json:"icon,omitempty"`
- IconColor string `json:"icon:color,omitempty"`
-
- Frame bool `json:"frame,omitempty"`
- FrameBorderColor string `json:"frame:bordercolor,omitempty"`
- FrameBorderColor_Focused string `json:"frame:bordercolor:focused,omitempty"`
-
- Cmd string `json:"cmd,omitempty"`
- CmdInteractive bool `json:"cmd:interactive,omitempty"`
- CmdLogin bool `json:"cmd:login,omitempty"`
- CmdRunOnStart bool `json:"cmd:runonstart,omitempty"`
- CmdClearOnStart bool `json:"cmd:clearonstart,omitempty"`
- CmdClearOnRestart bool `json:"cmd:clearonrestart,omitempty"`
- CmdEnv map[string]string `json:"cmd:env,omitempty"`
- CmdCwd string `json:"cmd:cwd,omitempty"`
-
- Bg string `json:"bg,omitempty"`
- BgOpacity float64 `json:"bg:opacity,omitempty"`
- BgBlendMode string `json:"bg:blendmode,omitempty"`
-}
-
type UIContext struct {
WindowId string `json:"windowid"`
ActiveTabId string `json:"activetabid"`
@@ -161,12 +113,11 @@ func (update *WaveObjUpdate) UnmarshalJSON(data []byte) error {
}
type Client struct {
- OID string `json:"oid"`
- Version int `json:"version"`
- MainWindowId string `json:"mainwindowid"` // deprecated
- WindowIds []string `json:"windowids"`
- Meta map[string]any `json:"meta"`
- TosAgreed int64 `json:"tosagreed,omitempty"`
+ OID string `json:"oid"`
+ Version int `json:"version"`
+ WindowIds []string `json:"windowids"`
+ Meta MetaMapType `json:"meta"`
+ TosAgreed int64 `json:"tosagreed,omitempty"`
}
func (*Client) GetOType() string {
@@ -185,7 +136,7 @@ type Window struct {
Pos Point `json:"pos"`
WinSize WinSize `json:"winsize"`
LastFocusTs int64 `json:"lastfocusts"`
- Meta map[string]any `json:"meta"`
+ Meta MetaMapType `json:"meta"`
}
func (*Window) GetOType() string {
@@ -193,11 +144,11 @@ func (*Window) GetOType() string {
}
type Workspace struct {
- OID string `json:"oid"`
- Version int `json:"version"`
- Name string `json:"name"`
- TabIds []string `json:"tabids"`
- Meta map[string]any `json:"meta"`
+ OID string `json:"oid"`
+ Version int `json:"version"`
+ Name string `json:"name"`
+ TabIds []string `json:"tabids"`
+ Meta MetaMapType `json:"meta"`
}
func (*Workspace) GetOType() string {
@@ -205,12 +156,12 @@ func (*Workspace) GetOType() string {
}
type Tab struct {
- OID string `json:"oid"`
- Version int `json:"version"`
- Name string `json:"name"`
- LayoutNode string `json:"layoutnode"`
- BlockIds []string `json:"blockids"`
- Meta map[string]any `json:"meta"`
+ OID string `json:"oid"`
+ Version int `json:"version"`
+ Name string `json:"name"`
+ LayoutNode string `json:"layoutnode"`
+ BlockIds []string `json:"blockids"`
+ Meta MetaMapType `json:"meta"`
}
func (*Tab) GetOType() string {
@@ -226,11 +177,11 @@ func (t *Tab) GetBlockORefs() []waveobj.ORef {
}
type LayoutNode struct {
- OID string `json:"oid"`
- Version int `json:"version"`
- Node any `json:"node,omitempty"`
- MagnifiedNodeId string `json:"magnifiednodeid,omitempty"`
- Meta map[string]any `json:"meta,omitempty"`
+ OID string `json:"oid"`
+ Version int `json:"version"`
+ Node any `json:"node,omitempty"`
+ MagnifiedNodeId string `json:"magnifiednodeid,omitempty"`
+ Meta MetaMapType `json:"meta,omitempty"`
}
func (*LayoutNode) GetOType() string {
@@ -246,10 +197,8 @@ type FileDef struct {
}
type BlockDef struct {
- Controller string `json:"controller,omitempty"`
- View string `json:"view,omitempty"`
- Files map[string]*FileDef `json:"files,omitempty"`
- Meta map[string]any `json:"meta,omitempty"`
+ Files map[string]*FileDef `json:"files,omitempty"`
+ Meta MetaMapType `json:"meta,omitempty"`
}
type StickerClickOptsType struct {
@@ -289,11 +238,9 @@ type Block struct {
OID string `json:"oid"`
Version int `json:"version"`
BlockDef *BlockDef `json:"blockdef"`
- Controller string `json:"controller"`
- View string `json:"view"`
RuntimeOpts *RuntimeOpts `json:"runtimeopts,omitempty"`
Stickers []*StickerType `json:"stickers,omitempty"`
- Meta map[string]any `json:"meta"`
+ Meta MetaMapType `json:"meta"`
}
func (*Block) GetOType() string {