mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
metadata updates (frontend typing) (#174)
This commit is contained in:
@@ -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/",
|
||||
},
|
||||
};
|
||||
|
||||
Vendored
+42
-17
@@ -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
|
||||
}
|
||||
|
||||
+13
-12
@@ -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
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user