edit variable definition
This commit is contained in:
1 parent
46d13cac7b
commit
b9345c6706
14 files changed
+503
-32
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"about": "About Behavior3",
|
"about": "About Behavior3",
|
||||||
|
"add": "Add",
|
||||||
"actuaSize": "Actual Size",
|
"actuaSize": "Actual Size",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"batch": "Batch Script",
|
"batch": "Batch Script",
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
"donotSave": "Don't Save",
|
"donotSave": "Don't Save",
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"editSubtree": "Edit Subtree",
|
"editSubtree": "Edit Subtree",
|
||||||
|
"fieldRequired": "Field '{{field}}' is required",
|
||||||
"forceReload": "Force Reload",
|
"forceReload": "Force Reload",
|
||||||
"fullscreen": "Toggle Full Screen",
|
"fullscreen": "Toggle Full Screen",
|
||||||
"hide": "Hide Behavior3",
|
"hide": "Hide Behavior3",
|
||||||
@@ -77,6 +79,11 @@
|
|||||||
"tree.desc": "Tree desc",
|
"tree.desc": "Tree desc",
|
||||||
"tree.export": "Export",
|
"tree.export": "Export",
|
||||||
"tree.firstid": "First id",
|
"tree.firstid": "First id",
|
||||||
|
"tree.vars": "Variable Definition",
|
||||||
|
"tree.vars.name": "name",
|
||||||
|
"tree.vars.invalidName": "Invalid variable name",
|
||||||
|
"tree.vars.desc": "description",
|
||||||
|
"tree.vars.imports": "Variable References",
|
||||||
|
|
||||||
"regnode.input": "Input: ",
|
"regnode.input": "Input: ",
|
||||||
"regnode.output": "Output: ",
|
"regnode.output": "Output: ",
|
||||||
@@ -85,9 +92,6 @@
|
|||||||
"regnode.subtree": "Subtree: ",
|
"regnode.subtree": "Subtree: ",
|
||||||
|
|
||||||
"node.args": "Const arguments",
|
"node.args": "Const arguments",
|
||||||
"node.args.add": "Add Sub Item",
|
|
||||||
"node.input.add": "Add Input Item",
|
|
||||||
"node.output.add": "Add Output Item",
|
|
||||||
"node.children.unlimited": "Unlimited",
|
"node.children.unlimited": "Unlimited",
|
||||||
"node.children": "Num children",
|
"node.children": "Num children",
|
||||||
"node.debug": "Debug",
|
"node.debug": "Debug",
|
||||||
@@ -95,7 +99,6 @@
|
|||||||
"node.desc": "Node desc",
|
"node.desc": "Node desc",
|
||||||
"node.disabled": "Disabled",
|
"node.disabled": "Disabled",
|
||||||
"node.editSubtreeDenied": "Unable to edit the subtree node",
|
"node.editSubtreeDenied": "Unable to edit the subtree node",
|
||||||
"node.fileRequired": "Field '{{field}}' is required",
|
|
||||||
"node.id": "Node id",
|
"node.id": "Node id",
|
||||||
"node.inputVariable": "Input variables",
|
"node.inputVariable": "Input variables",
|
||||||
"node.invalidValue": "Invalid value",
|
"node.invalidValue": "Invalid value",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"about": "关于 Behavior3",
|
"about": "关于 Behavior3",
|
||||||
|
"add": "添加",
|
||||||
"actualSize": "实际大小",
|
"actualSize": "实际大小",
|
||||||
"build": "构建",
|
"build": "构建",
|
||||||
"batch": "批处理脚本",
|
"batch": "批处理脚本",
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
"donotSave": "不保存",
|
"donotSave": "不保存",
|
||||||
"duplicate": "生成副本",
|
"duplicate": "生成副本",
|
||||||
"editSubtree": "编辑子树",
|
"editSubtree": "编辑子树",
|
||||||
|
"fieldRequired": "字段“{{field}}”必填",
|
||||||
"forceReload": "强制重新加载",
|
"forceReload": "强制重新加载",
|
||||||
"fullscreen": "切换全屏",
|
"fullscreen": "切换全屏",
|
||||||
"hide": "隐藏 Behavior3",
|
"hide": "隐藏 Behavior3",
|
||||||
@@ -77,6 +79,11 @@
|
|||||||
"tree.desc": "说明",
|
"tree.desc": "说明",
|
||||||
"tree.export": "导出",
|
"tree.export": "导出",
|
||||||
"tree.firstid": "起始标识",
|
"tree.firstid": "起始标识",
|
||||||
|
"tree.vars": "变量定义",
|
||||||
|
"tree.vars.name": "变量名",
|
||||||
|
"tree.vars.invalidName": "变量名不合法",
|
||||||
|
"tree.vars.desc": "变量说明",
|
||||||
|
"tree.vars.imports": "变量引用",
|
||||||
|
|
||||||
"regnode.input": "输入:",
|
"regnode.input": "输入:",
|
||||||
"regnode.output": "输出:",
|
"regnode.output": "输出:",
|
||||||
@@ -85,9 +92,6 @@
|
|||||||
"regnode.subtree": "子树:",
|
"regnode.subtree": "子树:",
|
||||||
|
|
||||||
"node.args": "常量参数",
|
"node.args": "常量参数",
|
||||||
"node.args.add": "添加子项",
|
|
||||||
"node.input.add": "添加输入项",
|
|
||||||
"node.output.add": "添加输出项",
|
|
||||||
"node.children.unlimited": "无限制",
|
"node.children.unlimited": "无限制",
|
||||||
"node.children": "子节点数量",
|
"node.children": "子节点数量",
|
||||||
"node.debug": "调试",
|
"node.debug": "调试",
|
||||||
@@ -95,7 +99,6 @@
|
|||||||
"node.desc": "节点说明",
|
"node.desc": "节点说明",
|
||||||
"node.disabled": "禁用",
|
"node.disabled": "禁用",
|
||||||
"node.editSubtreeDenied": "不能编辑子树节点",
|
"node.editSubtreeDenied": "不能编辑子树节点",
|
||||||
"node.fileRequired": "字段“{{field}}”必填",
|
|
||||||
"node.id": "节点标识",
|
"node.id": "节点标识",
|
||||||
"node.inputVariable": "输入变量",
|
"node.inputVariable": "输入变量",
|
||||||
"node.invalidValue": "无效的值",
|
"node.invalidValue": "无效的值",
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "def-core",
|
||||||
|
"desc": "",
|
||||||
|
"firstid": 1,
|
||||||
|
"export": true,
|
||||||
|
"declare": {
|
||||||
|
"imports": [],
|
||||||
|
"vars": [
|
||||||
|
{
|
||||||
|
"name": "context",
|
||||||
|
"desc": "上下文"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "version",
|
||||||
|
"desc": "版本"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "declare-vars",
|
||||||
|
"desc": "",
|
||||||
|
"firstid": 1,
|
||||||
|
"export": true,
|
||||||
|
"declare": {
|
||||||
|
"imports": [
|
||||||
|
"vars/declare-core.json"
|
||||||
|
],
|
||||||
|
"vars": [
|
||||||
|
{
|
||||||
|
"name": "owner",
|
||||||
|
"desc": "所有者"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "skill",
|
||||||
|
"desc": "技能"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "test-subtree",
|
||||||
|
"desc": "",
|
||||||
|
"firstid": 1,
|
||||||
|
"export": true,
|
||||||
|
"declare": {
|
||||||
|
"imports": [],
|
||||||
|
"vars": []
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Log",
|
||||||
|
"args": {
|
||||||
|
"message": "hello"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "test-vars",
|
||||||
|
"desc": "",
|
||||||
|
"firstid": 1,
|
||||||
|
"export": true,
|
||||||
|
"declare": {
|
||||||
|
"imports": [
|
||||||
|
"vars/declare-core.json",
|
||||||
|
"vars/declare-vars.json"
|
||||||
|
],
|
||||||
|
"vars": [
|
||||||
|
{
|
||||||
|
"name": "target",
|
||||||
|
"desc": "目标"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Sequence",
|
||||||
|
"path": "vars/test-subtree.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "FindEnemy",
|
||||||
|
"args": {},
|
||||||
|
"output": [
|
||||||
|
"target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "Attack",
|
||||||
|
"input": [
|
||||||
|
"target"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Check",
|
||||||
|
"args": {
|
||||||
|
"value": "target.x > 0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,21 @@
|
|||||||
{
|
{
|
||||||
"files": [
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "vars/declare-core.json",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "vars/declare-vars.json",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "vars/test-subtree.json",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "vars/test-vars.json",
|
||||||
|
"desc": ""
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "workdir/hero.json",
|
"path": "workdir/hero.json",
|
||||||
"desc": "英雄测试AI"
|
"desc": "英雄测试AI"
|
||||||
|
|||||||
@@ -464,6 +464,8 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
editor.export = editTree.data.export !== false;
|
editor.export = editTree.data.export !== false;
|
||||||
if (editor.data.firstid !== editTree.data.firstid) {
|
if (editor.data.firstid !== editTree.data.firstid) {
|
||||||
editor.firstid = editTree.data.firstid ?? 1;
|
editor.firstid = editTree.data.firstid ?? 1;
|
||||||
|
editor.declare.vars = editTree.data.declare.vars || [];
|
||||||
|
editor.declare.imports = editTree.data.declare.imports || [];
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
onChange();
|
onChange();
|
||||||
@@ -1042,7 +1044,10 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
useKeyDown(Hotkey.Undo, keysRef, () => undo());
|
useKeyDown(Hotkey.Undo, keysRef, () => undo());
|
||||||
useKeyDown(Hotkey.Redo, keysRef, () => redo());
|
useKeyDown(Hotkey.Redo, keysRef, () => redo());
|
||||||
useKeyDown([Hotkey.Insert, Hotkey.Enter], keysRef, () => createNode());
|
useKeyDown([Hotkey.Insert, Hotkey.Enter], keysRef, () => createNode());
|
||||||
useKeyDown([Hotkey.Delete, Hotkey.Backspace], keysRef, () => deleteNode());
|
useKeyDown([Hotkey.Delete, Hotkey.Backspace], keysRef, () => {
|
||||||
|
console.log("delete");
|
||||||
|
deleteNode();
|
||||||
|
});
|
||||||
|
|
||||||
editor.dispatch = (event: EditEvent, data: unknown) => {
|
editor.dispatch = (event: EditEvent, data: unknown) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@@ -1055,6 +1060,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case "delete": {
|
case "delete": {
|
||||||
|
console.log("delete1");
|
||||||
deleteNode();
|
deleteNode();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1217,6 +1223,8 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
} else if ((e.ctrlKey || e.metaKey) && e.code === "KeyG") {
|
} else if ((e.ctrlKey || e.metaKey) && e.code === "KeyG") {
|
||||||
searchByType("id");
|
searchByType("id");
|
||||||
}
|
}
|
||||||
|
console.log(e.code);
|
||||||
|
e.stopPropagation();
|
||||||
};
|
};
|
||||||
|
|
||||||
const recursiveOpenCollapsed = (id: string) => {
|
const recursiveOpenCollapsed = (id: string) => {
|
||||||
@@ -1281,7 +1289,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
}
|
}
|
||||||
onFocus={() => setFilterOption({ ...filterOption, isFocused: true })}
|
onFocus={() => setFilterOption({ ...filterOption, isFocused: true })}
|
||||||
onBlur={() => setFilterOption({ ...filterOption, isFocused: false })}
|
onBlur={() => setFilterOption({ ...filterOption, isFocused: false })}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDownCapture={handleKeyDown}
|
||||||
suffix={
|
suffix={
|
||||||
<Flex gap="2px" style={{ alignItems: "center" }}>
|
<Flex gap="2px" style={{ alignItems: "center" }}>
|
||||||
{filterOption.filterType !== "id" && (
|
{filterOption.filterType !== "id" && (
|
||||||
|
|||||||
+266
-10
@@ -1,4 +1,4 @@
|
|||||||
import { EditOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
import { AimOutlined, EditOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Button,
|
Button,
|
||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputNumber,
|
InputNumber,
|
||||||
Select,
|
Select,
|
||||||
|
Space,
|
||||||
Switch,
|
Switch,
|
||||||
} from "antd";
|
} from "antd";
|
||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
@@ -19,6 +20,7 @@ import { useDebounceCallback } from "usehooks-ts";
|
|||||||
import { useShallow } from "zustand/react/shallow";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
import { EditNode, EditTree, useWorkspace } from "../contexts/workspace-context";
|
import { EditNode, EditTree, useWorkspace } from "../contexts/workspace-context";
|
||||||
import {
|
import {
|
||||||
|
ImportDef,
|
||||||
isBoolType,
|
isBoolType,
|
||||||
isEnumType,
|
isEnumType,
|
||||||
isExprType,
|
isExprType,
|
||||||
@@ -28,6 +30,7 @@ import {
|
|||||||
isStringType,
|
isStringType,
|
||||||
NodeModel,
|
NodeModel,
|
||||||
TreeGraphData,
|
TreeGraphData,
|
||||||
|
VarDef,
|
||||||
} from "../misc/b3type";
|
} from "../misc/b3type";
|
||||||
import {
|
import {
|
||||||
checkNodeArgValue,
|
checkNodeArgValue,
|
||||||
@@ -37,6 +40,7 @@ import {
|
|||||||
isNodeArgOptional,
|
isNodeArgOptional,
|
||||||
isValidVariableName,
|
isValidVariableName,
|
||||||
isVariadic,
|
isVariadic,
|
||||||
|
loadImportedVars,
|
||||||
} from "../misc/b3util";
|
} from "../misc/b3util";
|
||||||
import { Hotkey, isMacos } from "../misc/keys";
|
import { Hotkey, isMacos } from "../misc/keys";
|
||||||
import { mergeClassNames } from "../misc/util";
|
import { mergeClassNames } from "../misc/util";
|
||||||
@@ -50,6 +54,9 @@ const TreeInspector: FC = () => {
|
|||||||
useShallow((state) => ({
|
useShallow((state) => ({
|
||||||
editing: state.editing,
|
editing: state.editing,
|
||||||
editingTree: state.editingTree!,
|
editingTree: state.editingTree!,
|
||||||
|
allFiles: state.allFiles,
|
||||||
|
relative: state.relative,
|
||||||
|
fileTree: state.fileTree,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -58,13 +65,31 @@ const TreeInspector: FC = () => {
|
|||||||
// set form values
|
// set form values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = workspace.editingTree.data;
|
const data = workspace.editingTree.data;
|
||||||
|
loadImportedVars(data.declare.imports);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldValue("name", data.name);
|
form.setFieldValue("name", data.name);
|
||||||
form.setFieldValue("desc", data.desc);
|
form.setFieldValue("desc", data.desc);
|
||||||
form.setFieldValue("export", data.export !== false);
|
form.setFieldValue("export", data.export !== false);
|
||||||
form.setFieldValue("firstid", data.firstid);
|
form.setFieldValue("firstid", data.firstid);
|
||||||
|
form.setFieldValue("declare.vars", data.declare?.vars);
|
||||||
|
form.setFieldValue("declare.imports", data.declare?.imports);
|
||||||
}, [workspace.editingTree]);
|
}, [workspace.editingTree]);
|
||||||
|
|
||||||
|
// auto complete for subtree
|
||||||
|
const subtreeOptions = useMemo(() => {
|
||||||
|
const options: OptionType[] = [];
|
||||||
|
workspace.allFiles.forEach((file) => {
|
||||||
|
const value = workspace.relative(file.path);
|
||||||
|
const desc = ""; //fileNode.desc ? `(${fileNode.desc})` : "";
|
||||||
|
options.push({
|
||||||
|
label: `${value}${desc}`,
|
||||||
|
value: value,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
options.sort((a, b) => a.value.localeCompare(b.value));
|
||||||
|
return options;
|
||||||
|
}, [workspace.allFiles, workspace.fileTree]);
|
||||||
|
|
||||||
const finish = (values: any) => {
|
const finish = (values: any) => {
|
||||||
workspace.editing?.dispatch("updateTree", {
|
workspace.editing?.dispatch("updateTree", {
|
||||||
data: {
|
data: {
|
||||||
@@ -72,6 +97,18 @@ const TreeInspector: FC = () => {
|
|||||||
desc: values.desc || undefined,
|
desc: values.desc || undefined,
|
||||||
export: values.export,
|
export: values.export,
|
||||||
firstid: Number(values.firstid),
|
firstid: Number(values.firstid),
|
||||||
|
declare: {
|
||||||
|
vars: (values["declare.vars"] as VarDef[])
|
||||||
|
.filter((v) => v && v.name)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name)),
|
||||||
|
imports: (values["declare.imports"] as ImportDef[])
|
||||||
|
.filter((v) => v && v.path)
|
||||||
|
.sort((a, b) => a.path.localeCompare(b.path))
|
||||||
|
.map((v) => ({
|
||||||
|
path: v.path,
|
||||||
|
vars: v.vars ?? [],
|
||||||
|
})),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as EditTree);
|
} as EditTree);
|
||||||
};
|
};
|
||||||
@@ -85,7 +122,13 @@ const TreeInspector: FC = () => {
|
|||||||
className={mergeClassNames("b3-inspector-content", isMacos ? "" : "b3-overflow")}
|
className={mergeClassNames("b3-inspector-content", isMacos ? "" : "b3-overflow")}
|
||||||
style={{ overflow: "auto", height: "100%" }}
|
style={{ overflow: "auto", height: "100%" }}
|
||||||
>
|
>
|
||||||
<Form form={form} labelCol={{ span: 8 }} onFinish={finish}>
|
<Form
|
||||||
|
form={form}
|
||||||
|
wrapperCol={{ span: "auto" }}
|
||||||
|
labelCol={{ span: "auto" }}
|
||||||
|
onFinish={finish}
|
||||||
|
>
|
||||||
|
<>
|
||||||
<Form.Item name="name" label={t("tree.name")}>
|
<Form.Item name="name" label={t("tree.name")}>
|
||||||
<Input disabled={true} />
|
<Input disabled={true} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -98,6 +141,219 @@ const TreeInspector: FC = () => {
|
|||||||
<Form.Item name="export" label={t("tree.export")} valuePropName="checked">
|
<Form.Item name="export" label={t("tree.export")} valuePropName="checked">
|
||||||
<Switch onChange={() => form.submit()} />
|
<Switch onChange={() => form.submit()} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
<>
|
||||||
|
<Divider orientation="left">
|
||||||
|
<h4>{t("tree.vars")}</h4>
|
||||||
|
</Divider>
|
||||||
|
<Form.List name="declare.vars">
|
||||||
|
{(items, { add, remove }, { errors }) => (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", rowGap: 0 }}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Flex key={item.key} gap={4}>
|
||||||
|
<Space.Compact
|
||||||
|
className="b3-inspector-vars-item"
|
||||||
|
style={{ width: "100%", marginBottom: 2 }}
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
name={[item.name, "name"]}
|
||||||
|
validateTrigger={["onChange", "onBlur"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
validator(_, value) {
|
||||||
|
if (value && /^[_\w]+\w*$/.test(value)) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(t("tree.vars.invalidName")));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
addonBefore={
|
||||||
|
<div style={{ display: "flex", alignItems: "center", width: "45px" }}>
|
||||||
|
<AimOutlined />
|
||||||
|
<span style={{ marginLeft: 4 }}>0</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
placeholder={t("tree.vars.name")}
|
||||||
|
onBlur={form.submit}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
name={[item.name, "desc"]}
|
||||||
|
validateTrigger={["onChange", "onBlur"]}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t("fieldRequired", { field: t("tree.vars.desc") }),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
style={{ width: "75%" }}
|
||||||
|
placeholder={t("tree.vars.desc")}
|
||||||
|
onBlur={form.submit}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginBottom: "6px" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(item.name);
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
<Form.Item
|
||||||
|
style={{
|
||||||
|
marginRight: items.length === 0 ? undefined : "18px",
|
||||||
|
marginTop: 4,
|
||||||
|
alignItems: "end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add({});
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t("add")}
|
||||||
|
</Button>
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</>
|
||||||
|
<>
|
||||||
|
<Divider orientation="left">
|
||||||
|
<h4>{t("tree.vars.imports")}</h4>
|
||||||
|
</Divider>
|
||||||
|
<Form.List name="declare.imports">
|
||||||
|
{(items, { add, remove }, { errors }) => (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", rowGap: 4 }}>
|
||||||
|
{items.map((item) => (
|
||||||
|
<Space.Compact
|
||||||
|
key={item.key}
|
||||||
|
className="b3-inspector-import-item"
|
||||||
|
direction="vertical"
|
||||||
|
style={{ marginBottom: 5 }}
|
||||||
|
>
|
||||||
|
<Flex gap={4} style={{ width: "100%" }}>
|
||||||
|
<Form.Item
|
||||||
|
name={[item.name, "path"]}
|
||||||
|
style={{ width: "100%", marginBottom: 2 }}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
showSearch
|
||||||
|
options={subtreeOptions}
|
||||||
|
onBlur={form.submit}
|
||||||
|
onInputKeyDown={(e) => e.code === Hotkey.Escape && e.preventDefault()}
|
||||||
|
filterOption={(value, option) => {
|
||||||
|
const label = option!.label as string;
|
||||||
|
return label.toUpperCase().includes(value.toUpperCase());
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginBottom: "6px" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(item.name);
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
<Form.List name={[item.name, "vars"]}>
|
||||||
|
{(vars) => (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column", rowGap: 0 }}>
|
||||||
|
{vars.map((v, idx, arr) => (
|
||||||
|
<Flex key={v.key} gap={4}>
|
||||||
|
<Space.Compact
|
||||||
|
className="b3-inspector-vars-item"
|
||||||
|
style={{ width: "100%", marginBottom: 0, paddingRight: 18 }}
|
||||||
|
>
|
||||||
|
<Form.Item noStyle name={[v.name, "id"]}>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingLeft: "8px",
|
||||||
|
paddingRight: "8px",
|
||||||
|
maxWidth: "60px",
|
||||||
|
minWidth: "60px",
|
||||||
|
borderTopLeftRadius: idx === 0 ? "4px" : 0,
|
||||||
|
borderBottomLeftRadius: idx === arr.length - 1 ? "4px" : 0,
|
||||||
|
borderLeft: "1px solid #3d506c",
|
||||||
|
borderTop: "1px solid #3d506c",
|
||||||
|
borderBottom:
|
||||||
|
idx === arr.length - 1 ? "1px solid #3d506c" : "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AimOutlined />
|
||||||
|
<span style={{ marginLeft: 4 }}>0</span>
|
||||||
|
</div>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={[v.name, "name"]}>
|
||||||
|
<Input
|
||||||
|
disabled={true}
|
||||||
|
placeholder={t("tree.vars.name")}
|
||||||
|
style={{
|
||||||
|
borderRadius: "0",
|
||||||
|
borderTop: "1px solid #3d506c",
|
||||||
|
borderBottom:
|
||||||
|
idx === arr.length - 1 ? "1px solid #3d506c" : "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item noStyle name={[v.name, "desc"]}>
|
||||||
|
<Input
|
||||||
|
disabled={true}
|
||||||
|
placeholder={t("tree.vars.desc")}
|
||||||
|
style={{
|
||||||
|
borderTopRightRadius: idx === 0 ? "4px" : 0,
|
||||||
|
borderBottomRightRadius: idx === arr.length - 1 ? "4px" : 0,
|
||||||
|
borderTop: "1px solid #3d506c",
|
||||||
|
borderBottom:
|
||||||
|
idx === arr.length - 1 ? "1px solid #3d506c" : "none",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Space.Compact>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Space.Compact>
|
||||||
|
))}
|
||||||
|
<Form.Item
|
||||||
|
style={{
|
||||||
|
marginRight: items.length === 0 ? undefined : "18px",
|
||||||
|
alignItems: "end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add({});
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t("add")}
|
||||||
|
</Button>
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -490,7 +746,7 @@ const NodeInspector: FC = () => {
|
|||||||
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
>
|
>
|
||||||
{t("node.input.add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
<Form.ErrorList errors={errors} />
|
<Form.ErrorList errors={errors} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -506,7 +762,7 @@ const NodeInspector: FC = () => {
|
|||||||
name={`input.${i}`}
|
name={`input.${i}`}
|
||||||
key={`input.${i}`}
|
key={`input.${i}`}
|
||||||
rules={[
|
rules={[
|
||||||
{ required, message: t("node.fileRequired", { field: desc }) },
|
{ required, message: t("fieldRequired", { field: desc }) },
|
||||||
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
if (value && !isValidVariableName(value)) {
|
if (value && !isValidVariableName(value)) {
|
||||||
@@ -548,7 +804,7 @@ const NodeInspector: FC = () => {
|
|||||||
onInputKeyDown={(e) => e.code === Hotkey.Escape && e.preventDefault()}
|
onInputKeyDown={(e) => e.code === Hotkey.Escape && e.preventDefault()}
|
||||||
filterOption={(inputValue: string, option?: OptionType) => {
|
filterOption={(inputValue: string, option?: OptionType) => {
|
||||||
const label = option!.label as string;
|
const label = option!.label as string;
|
||||||
return label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
|
return label.toUpperCase().includes(inputValue.toUpperCase());
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -585,7 +841,7 @@ const NodeInspector: FC = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required,
|
required,
|
||||||
message: t("node.fileRequired", { field: arg.desc }),
|
message: t("fieldRequired", { field: arg.desc }),
|
||||||
},
|
},
|
||||||
() => ({
|
() => ({
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
@@ -681,7 +937,7 @@ const NodeInspector: FC = () => {
|
|||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
danger={items.length === 0 && !isNodeArgOptional(arg)}
|
danger={items.length === 0 && !isNodeArgOptional(arg)}
|
||||||
>
|
>
|
||||||
{t("node.args.add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
<Form.ErrorList errors={errors} />
|
<Form.ErrorList errors={errors} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -699,7 +955,7 @@ const NodeInspector: FC = () => {
|
|||||||
initialValue={isBoolType(type) ? arg.default ?? false : arg.default}
|
initialValue={isBoolType(type) ? arg.default ?? false : arg.default}
|
||||||
valuePropName={isBoolType(type) ? "checked" : undefined}
|
valuePropName={isBoolType(type) ? "checked" : undefined}
|
||||||
rules={[
|
rules={[
|
||||||
{ required, message: t("node.fileRequired", { field: arg.desc }) },
|
{ required, message: t("fieldRequired", { field: arg.desc }) },
|
||||||
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
if (value && isJsonType(type)) {
|
if (value && isJsonType(type)) {
|
||||||
@@ -861,7 +1117,7 @@ const NodeInspector: FC = () => {
|
|||||||
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
||||||
icon={<PlusOutlined />}
|
icon={<PlusOutlined />}
|
||||||
>
|
>
|
||||||
{t("node.output.add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
<Form.ErrorList errors={errors} />
|
<Form.ErrorList errors={errors} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -877,7 +1133,7 @@ const NodeInspector: FC = () => {
|
|||||||
name={`output.${i}`}
|
name={`output.${i}`}
|
||||||
key={`output.${i}`}
|
key={`output.${i}`}
|
||||||
rules={[
|
rules={[
|
||||||
{ required, message: t("node.fileRequired", { field: desc }) },
|
{ required, message: t("fieldRequired", { field: desc }) },
|
||||||
{
|
{
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
if (value && !isValidVariableName(value)) {
|
if (value && !isValidVariableName(value)) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import * as fs from "fs";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { NodeDef } from "../behavior3/src/behavior3";
|
import { NodeDef } from "../behavior3/src/behavior3";
|
||||||
import { NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
|
import { ImportDef, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
|
||||||
import * as b3util from "../misc/b3util";
|
import * as b3util from "../misc/b3util";
|
||||||
import { message } from "../misc/hooks";
|
import { message } from "../misc/hooks";
|
||||||
import i18n from "../misc/i18n";
|
import i18n from "../misc/i18n";
|
||||||
@@ -50,10 +50,7 @@ export class EditorStore {
|
|||||||
firstid: number;
|
firstid: number;
|
||||||
|
|
||||||
declare: {
|
declare: {
|
||||||
imports: {
|
imports: ImportDef[];
|
||||||
path: string;
|
|
||||||
vars: VarDef[];
|
|
||||||
}[];
|
|
||||||
vars: VarDef[];
|
vars: VarDef[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -122,7 +119,16 @@ export type EditNodeDef = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type EditTree = {
|
export type EditTree = {
|
||||||
data: TreeModel;
|
data: {
|
||||||
|
name: string;
|
||||||
|
desc?: string;
|
||||||
|
export?: boolean;
|
||||||
|
firstid?: number;
|
||||||
|
declare: {
|
||||||
|
imports: ImportDef[];
|
||||||
|
vars: VarDef[];
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FileMeta = {
|
export type FileMeta = {
|
||||||
@@ -511,7 +517,10 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
name: editting.name,
|
name: editting.name,
|
||||||
desc: editting.desc,
|
desc: editting.desc,
|
||||||
firstid: editting.firstid,
|
firstid: editting.firstid,
|
||||||
root: null!,
|
declare: {
|
||||||
|
vars: editting.declare.vars,
|
||||||
|
imports: editting.declare.imports,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -666,7 +675,10 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
desc: editor.desc,
|
desc: editor.desc,
|
||||||
export: editor.export,
|
export: editor.export,
|
||||||
firstid: editor.firstid,
|
firstid: editor.firstid,
|
||||||
root: null!,
|
declare: {
|
||||||
|
imports: editor.declare.imports,
|
||||||
|
vars: editor.declare.vars,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
editingNodeDef: null,
|
editingNodeDef: null,
|
||||||
|
|||||||
@@ -124,6 +124,11 @@ body {
|
|||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.b3-inspector-vars-item .ant-input-group-addon {
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
:focus {
|
:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ export interface VarDef {
|
|||||||
desc: string;
|
desc: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ImportDef {
|
||||||
|
path: string;
|
||||||
|
date?: number;
|
||||||
|
vars: VarDef[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface TreeModel {
|
export interface TreeModel {
|
||||||
name: string;
|
name: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
|
|||||||
+38
-4
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { NodeDef } from "../behavior3/src/behavior3";
|
import { type NodeDef } from "../behavior3/src/behavior3";
|
||||||
import {
|
import {
|
||||||
|
ImportDef,
|
||||||
isBoolType,
|
isBoolType,
|
||||||
isEnumType,
|
isEnumType,
|
||||||
isExprType,
|
isExprType,
|
||||||
@@ -12,9 +13,10 @@ import {
|
|||||||
NodeModel,
|
NodeModel,
|
||||||
TreeGraphData,
|
TreeGraphData,
|
||||||
TreeModel,
|
TreeModel,
|
||||||
|
VarDef,
|
||||||
} from "./b3type";
|
} from "./b3type";
|
||||||
import Path from "./path";
|
import Path from "./path";
|
||||||
import { readJson } from "./util";
|
import { readJson, readTree } from "./util";
|
||||||
|
|
||||||
export class NodeDefs extends Map<string, NodeDef> {
|
export class NodeDefs extends Map<string, NodeDef> {
|
||||||
get(key: string): NodeDef {
|
get(key: string): NodeDef {
|
||||||
@@ -603,8 +605,7 @@ export const createTreeData = (
|
|||||||
parsingStack.push(node.path);
|
parsingStack.push(node.path);
|
||||||
try {
|
try {
|
||||||
const subtreePath = workdir + "/" + node.path;
|
const subtreePath = workdir + "/" + node.path;
|
||||||
const str = fs.readFileSync(subtreePath, "utf8");
|
treeData = createTreeData(readTree(subtreePath).root, treeData.id, calcSize);
|
||||||
treeData = createTreeData(JSON.parse(str).root, treeData.id, calcSize);
|
|
||||||
treeData.lastModified = fs.statSync(subtreePath).mtimeMs;
|
treeData.lastModified = fs.statSync(subtreePath).mtimeMs;
|
||||||
treeData.path = node.path;
|
treeData.path = node.path;
|
||||||
treeData.debug = node.debug;
|
treeData.debug = node.debug;
|
||||||
@@ -685,3 +686,36 @@ export const createNewTree = (path: string) => {
|
|||||||
export const isTreeFile = (path: string) => {
|
export const isTreeFile = (path: string) => {
|
||||||
return path.toLocaleLowerCase().endsWith(".json");
|
return path.toLocaleLowerCase().endsWith(".json");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadImportedVars = (imports: ImportDef[]) => {
|
||||||
|
for (const importDef of imports) {
|
||||||
|
// TODO: check file date
|
||||||
|
// TODO: use cache
|
||||||
|
if (importDef.vars.length) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const filter: Map<string, boolean> = new Map();
|
||||||
|
const vars: VarDef[] = [];
|
||||||
|
const load = (path: string) => {
|
||||||
|
if (filter.has(path)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
filter.set(path, true);
|
||||||
|
const model: TreeModel = readTree(`${workdir}/${path}`);
|
||||||
|
model.declare?.vars?.forEach((v) => vars.push(v));
|
||||||
|
model.declare?.imports?.forEach((v) => load(v));
|
||||||
|
console.log("load imported vars:", path);
|
||||||
|
};
|
||||||
|
load(importDef.path);
|
||||||
|
filter.clear();
|
||||||
|
importDef.vars = vars
|
||||||
|
.filter((v) => {
|
||||||
|
if (!filter.has(v.name)) {
|
||||||
|
filter.set(v.name, true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,10 +1,16 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import type { TreeModel } from "./b3type";
|
||||||
|
|
||||||
export const readJson = (path: string) => {
|
export const readJson = (path: string) => {
|
||||||
const str = fs.readFileSync(path, "utf-8");
|
const str = fs.readFileSync(path, "utf-8");
|
||||||
return JSON.parse(str);
|
return JSON.parse(str);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const readTree = (path: string) => {
|
||||||
|
const str = fs.readFileSync(path, "utf-8");
|
||||||
|
return JSON.parse(str) as TreeModel;
|
||||||
|
};
|
||||||
|
|
||||||
export const writeJson = (path: string, data: any) => {
|
export const writeJson = (path: string, data: any) => {
|
||||||
const str = JSON.stringify(data, undefined, 2);
|
const str = JSON.stringify(data, undefined, 2);
|
||||||
fs.writeFileSync(path, str, "utf-8");
|
fs.writeFileSync(path, str, "utf-8");
|
||||||
|
|||||||
Reference in new issue
Block a user