mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-21 16:38:23 +01:00
metadata updates (frontend typing) (#174)
This commit is contained in:
parent
9233b3dbd7
commit
cfc875bc21
@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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 };
|
||||
|
@ -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,
|
||||
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -24,11 +24,6 @@
|
||||
.filler {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
> * {
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-msg {
|
||||
|
@ -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/",
|
||||
},
|
||||
};
|
||||
|
59
frontend/types/gotypes.d.ts
vendored
59
frontend/types/gotypes.d.ts
vendored
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
74
pkg/waveobj/metamap.go
Normal 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
|
||||
}
|
@ -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) {
|
||||
|
@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
153
pkg/wstore/wstore_meta.go
Normal 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
|
||||
}
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user