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
27 changed files with 482 additions and 300 deletions
+2 -2
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,
},
},
}
+2 -2
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;
}
+22 -13
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(
+4 -4
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 };
+2 -2
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,
+2 -2
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",
+1 -1
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);
-5
View File
@@ -24,11 +24,6 @@
.filler {
flex: 1 1 auto;
}
> * {
cursor: default;
user-select: none;
}
}
.chat-msg {
+6 -4
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/",
},
};
+42 -17
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
+28 -67
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,
+40
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
}
+3 -2
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 {
+3 -2
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)
+1 -1
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
}
+13 -12
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
}
+12 -1
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
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
}
+9 -6
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) {
+18 -6
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",
},
},
},
}

Some files were not shown because too many files have changed in this diff Show More