metadata updates (frontend typing) (#174)

This commit is contained in:
Mike Sawka 2024-07-30 12:33:28 -07:00 committed by GitHub
parent 9233b3dbd7
commit cfc875bc21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 482 additions and 300 deletions

View File

@ -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,
},
},
}

View File

@ -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;
}

View File

@ -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 (
<div
className={clsx(
@ -397,6 +401,7 @@ const BlockFrame_Default_Component = ({
<div
className="block-frame-default-header"
ref={layoutModel?.dragHandleRef}
onDoubleClick={handleDoubleClick}
onContextMenu={(e) =>
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<HTMLDivElement>
): { 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 = <CenteredDiv>No View</CenteredDiv>;
viewModel = makeDefaultViewModel(blockId);
} else if (blockView === "term") {
const termViewModel = makeTerminalModel(blockId);
viewElem = <TerminalView key={blockId} blockId={blockId} model={termViewModel} />;
viewModel = termViewModel;
@ -501,6 +509,7 @@ function getViewElemAndModel(
viewModel = waveAiModel;
}
if (viewModel == null) {
viewElem = <CenteredDiv>Invalid View "{blockView}"</CenteredDiv>;
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 (
<BlockFrame
key={blockId}
@ -588,8 +597,8 @@ const BlockFull = React.memo(({ blockId, layoutModel }: BlockProps) => {
}, []);
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(

View File

@ -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 };

View File

@ -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,

View File

@ -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",

View File

@ -16,7 +16,7 @@ const TermThemeUpdater = ({ blockId, termRef }: TermThemeProps) => {
const { termthemes } = useAtomValue(atoms.settingsConfigAtom);
const [blockData] = WOS.useWaveObjectValue<Block>(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);

View File

@ -24,11 +24,6 @@
.filler {
flex: 1 1 auto;
}
> * {
cursor: default;
user-select: none;
}
}
.chat-msg {

View File

@ -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/",
},
};

View File

@ -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

View File

@ -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,

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

74
pkg/waveobj/metamap.go Normal file
View File

@ -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
}

View File

@ -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) {

View File

@ -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",
},
},
},
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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)

View File

@ -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
}

153
pkg/wstore/wstore_meta.go Normal file
View File

@ -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
}

View File

@ -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 {