edit variable definition

This commit is contained in:
codetypes committed 2025-03-16 21:29:19 +08:00
1 parent 46d13cac7b
commit b9345c6706
14 files changed
+503 -32

No files matched your search

+7 -4
View File
@@ -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",
+7 -4
View File
@@ -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": "无效的值",
+23
View File
@@ -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"
}
}
+25
View File
@@ -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"
}
}
+23
View File
@@ -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"
}
}
]
}
}
+51
View File
@@ -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"
}
}
]
}
}
+16
View File
@@ -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"
+10 -2
View File
@@ -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
View File
@@ -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)) {
+20 -8
View File
@@ -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,
+5
View File
@@ -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;
} }
+6
View File
@@ -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
View File
@@ -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));
}
};
+6
View File
@@ -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");