check variable define
This commit is contained in:
1 parent
deaa12ff4b
commit
e8ae056461
7 files changed
+162
-40
No files matched your search
@@ -116,6 +116,7 @@
|
||||
"node.subtree": "Subtree",
|
||||
"node.subtreePathError": "You should save the subtree inside the current workspace",
|
||||
"node.type": "Node type",
|
||||
"node.undefinedVariable": "Variable '{{variable}}' is not defined",
|
||||
|
||||
"node.unknown.desc": "New node"
|
||||
}
|
||||
@@ -116,6 +116,7 @@
|
||||
"node.subtree": "子树",
|
||||
"node.subtreePathError": "子树必须保存在当前工作区的目录内",
|
||||
"node.type": "节点类型",
|
||||
"node.undefinedVariable": "变量 “{{variable}}” 未定义",
|
||||
|
||||
"node.unknown.desc": "新建节点"
|
||||
}
|
||||
@@ -15,6 +15,14 @@
|
||||
{
|
||||
"name": "target",
|
||||
"desc": "目标"
|
||||
},
|
||||
{
|
||||
"name": "target1",
|
||||
"desc": "其它目标"
|
||||
},
|
||||
{
|
||||
"name": "test",
|
||||
"desc": "测试"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -32,21 +40,21 @@
|
||||
"name": "FindEnemy",
|
||||
"args": {},
|
||||
"output": [
|
||||
"target"
|
||||
"target1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Attack",
|
||||
"input": [
|
||||
"target"
|
||||
"target1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"name": "Check",
|
||||
"args": {
|
||||
"value": "target.x > 0"
|
||||
"value": "target1.x > 0"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -21,8 +21,9 @@ import {
|
||||
EditTree,
|
||||
useWorkspace,
|
||||
} from "../contexts/workspace-context";
|
||||
import { isExprType, NodeModel, TreeGraphData, TreeModel } from "../misc/b3type";
|
||||
import { ImportDef, isExprType, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
|
||||
import * as b3util from "../misc/b3util";
|
||||
import { loadSubtreeVarDef, loadVarDef } from "../misc/b3util";
|
||||
import { message } from "../misc/hooks";
|
||||
import i18n from "../misc/i18n";
|
||||
import { Hotkey, isMacos, useKeyDown } from "../misc/keys";
|
||||
@@ -47,6 +48,13 @@ interface FilterOption {
|
||||
isFocused: boolean;
|
||||
}
|
||||
|
||||
const collectVarDefs = (editor: EditorStore) => {
|
||||
const vars: VarDef[] = editor.declare.vars.slice();
|
||||
loadVarDef(editor.declare.imports).forEach((v) => vars.push(v));
|
||||
loadSubtreeVarDef(editor.data).forEach((v) => vars.push(v));
|
||||
return vars;
|
||||
};
|
||||
|
||||
const createTreeData = (node: NodeModel, parent?: string) => {
|
||||
return b3util.createTreeData(node, parent, calcTreeDataSize);
|
||||
};
|
||||
@@ -60,6 +68,42 @@ const refreshTreeData = (data: TreeGraphData) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isTreeUpdated = (editor: EditorStore, editTree: EditTree) => {
|
||||
const data = editTree.data;
|
||||
if (
|
||||
editor.firstid !== data.firstid ||
|
||||
editor.export !== data.export ||
|
||||
editor.name !== data.name ||
|
||||
editor.desc !== data.desc
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.max(editor.declare.vars.length, data.declare.vars.length); i++) {
|
||||
const v1: VarDef | undefined = editor.declare.vars[i];
|
||||
const v2: VarDef | undefined = data.declare.vars[i];
|
||||
if (v1?.name !== v2?.name || v1?.desc !== v2?.desc) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.max(editor.group.length, data.group.length); i++) {
|
||||
if (editor.group[i] !== data.group[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.max(editor.declare.imports.length, data.declare.imports.length); i++) {
|
||||
const v1: ImportDef | undefined = editor.declare.imports[i];
|
||||
const v2: ImportDef | undefined = data.declare.imports[i];
|
||||
if (v1?.path !== v2?.path) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isSubtreeUpdated = (data: TreeGraphData) => {
|
||||
if (data.path) {
|
||||
try {
|
||||
@@ -491,17 +535,16 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
||||
};
|
||||
|
||||
const updateTree = (editTree: EditTree) => {
|
||||
if (editor.desc !== editTree.data.desc || editor.export !== editTree.data.export) {
|
||||
if (isTreeUpdated(editor, editTree)) {
|
||||
editor.desc = editTree.data.desc || "";
|
||||
editor.export = editTree.data.export !== false;
|
||||
editor.group = editTree.data.group || [];
|
||||
editor.firstid = editTree.data.firstid ?? 1;
|
||||
editor.declare.vars = editTree.data.declare.vars || [];
|
||||
editor.declare.imports = editTree.data.declare.imports || [];
|
||||
b3util.updateUsingGroups(editor.group);
|
||||
if (editor.data.firstid !== editTree.data.firstid) {
|
||||
editor.firstid = editTree.data.firstid ?? 1;
|
||||
editor.declare.vars = editTree.data.declare.vars || [];
|
||||
editor.declare.imports = editTree.data.declare.imports || [];
|
||||
refresh();
|
||||
}
|
||||
b3util.updateUsingVars(collectVarDefs(editor));
|
||||
refresh();
|
||||
onChange();
|
||||
}
|
||||
};
|
||||
@@ -1207,6 +1250,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
||||
if (workspace.editing === editor) {
|
||||
checkSubtree();
|
||||
b3util.updateUsingGroups(editor.group);
|
||||
b3util.updateUsingVars(collectVarDefs(editor));
|
||||
}
|
||||
}, [workspace.editing]);
|
||||
|
||||
|
||||
@@ -43,7 +43,9 @@ import {
|
||||
isValidVariableName,
|
||||
isVariadic,
|
||||
loadVarDef,
|
||||
parseExpr,
|
||||
usingGroups,
|
||||
usingVars,
|
||||
} from "../misc/b3util";
|
||||
import { Hotkey, isMacos } from "../misc/keys";
|
||||
import { mergeClassNames } from "../misc/util";
|
||||
@@ -108,7 +110,7 @@ const TreeInspector: FC = () => {
|
||||
workspace.editing?.dispatch("updateTree", {
|
||||
data: {
|
||||
name: values.name,
|
||||
desc: values.desc || undefined,
|
||||
desc: values.desc,
|
||||
export: values.export,
|
||||
firstid: Number(values.firstid),
|
||||
group: (values.group as GroupDef[])
|
||||
@@ -568,6 +570,7 @@ const NodeInspector: FC = () => {
|
||||
// auto complete for input and output
|
||||
const inoutVarOptions = useMemo(() => {
|
||||
const options: OptionType[] = [];
|
||||
const filter: Record<string, boolean> = {};
|
||||
const collect = (node?: TreeGraphData) => {
|
||||
if (node) {
|
||||
node.input?.forEach((v, i) => {
|
||||
@@ -578,8 +581,9 @@ const NodeInspector: FC = () => {
|
||||
} else {
|
||||
desc = inputDef?.[i] ?? "<unknown>";
|
||||
}
|
||||
if (v && !options.find((option) => option.value === v)) {
|
||||
if (!filter[v]) {
|
||||
options.push({ label: `${v}(${desc})`, value: v });
|
||||
filter[v] = true;
|
||||
}
|
||||
});
|
||||
node.output?.forEach((v, i) => {
|
||||
@@ -590,16 +594,26 @@ const NodeInspector: FC = () => {
|
||||
} else {
|
||||
desc = outputDef?.[i] ?? "<unknown>";
|
||||
}
|
||||
if (v && !options.find((option) => option.value === v)) {
|
||||
if (!filter[v]) {
|
||||
options.push({ label: `${v}(${desc})`, value: v });
|
||||
filter[v] = true;
|
||||
}
|
||||
});
|
||||
node.children?.forEach((child) => collect(child));
|
||||
}
|
||||
};
|
||||
collect(workspace.editing?.data);
|
||||
if (usingVars) {
|
||||
Object.values(usingVars ?? {}).forEach((v) => {
|
||||
if (!filter[v.name]) {
|
||||
options.push({ label: `${v.name}(${v.desc})`, value: v.name });
|
||||
filter[v.name] = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
collect(workspace.editing?.data);
|
||||
}
|
||||
return options;
|
||||
}, [workspace.editing]);
|
||||
}, [workspace.editing, usingVars]);
|
||||
|
||||
// auto complete for subtree
|
||||
const subtreeOptions = useMemo(() => {
|
||||
@@ -753,7 +767,7 @@ const NodeInspector: FC = () => {
|
||||
rules={[
|
||||
{
|
||||
validator() {
|
||||
if (def.group && !usingGroups[def.group]) {
|
||||
if (def.group && usingGroups && !usingGroups[def.group]) {
|
||||
return Promise.reject(new Error(t("node.invalidGroup", { group: def.group })));
|
||||
}
|
||||
return Promise.resolve();
|
||||
@@ -833,6 +847,13 @@ const NodeInspector: FC = () => {
|
||||
rules={[
|
||||
{
|
||||
validator(_, value) {
|
||||
if (value && usingVars && !usingVars[value]) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
t("node.undefinedVariable", { variable: value })
|
||||
)
|
||||
);
|
||||
}
|
||||
if (value && !isValidVariableName(value)) {
|
||||
return Promise.reject(
|
||||
new Error(t("node.invalidVariableName"))
|
||||
@@ -895,6 +916,11 @@ const NodeInspector: FC = () => {
|
||||
{ required, message: t("fieldRequired", { field: desc }) },
|
||||
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
||||
validator(_, value) {
|
||||
if (value && usingVars && !usingVars[value]) {
|
||||
return Promise.reject(
|
||||
new Error(t("node.undefinedVariable", { variable: value }))
|
||||
);
|
||||
}
|
||||
if (value && !isValidVariableName(value)) {
|
||||
return Promise.reject(new Error(t("node.invalidVariableName")));
|
||||
}
|
||||
@@ -975,6 +1001,17 @@ const NodeInspector: FC = () => {
|
||||
},
|
||||
() => ({
|
||||
validator(_, value) {
|
||||
if (usingVars && isExprType(type) && value) {
|
||||
for (const v of parseExpr(value)) {
|
||||
if (!usingVars![v]) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
t("node.undefinedVariable", { variable: v })
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isJsonType(type)) {
|
||||
try {
|
||||
if (value !== "null") {
|
||||
@@ -1088,6 +1125,15 @@ const NodeInspector: FC = () => {
|
||||
{ required, message: t("fieldRequired", { field: arg.desc }) },
|
||||
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
||||
validator(_, value) {
|
||||
if (usingVars && isExprType(type) && value) {
|
||||
for (const v of parseExpr(value)) {
|
||||
if (!usingVars![v]) {
|
||||
return Promise.reject(
|
||||
new Error(t("node.undefinedVariable", { variable: v }))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (value && isJsonType(type)) {
|
||||
try {
|
||||
if (value !== "null") {
|
||||
@@ -1204,6 +1250,13 @@ const NodeInspector: FC = () => {
|
||||
rules={[
|
||||
{
|
||||
validator(_, value) {
|
||||
if (value && usingVars && !usingVars[value]) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
t("node.undefinedVariable", { variable: value })
|
||||
)
|
||||
);
|
||||
}
|
||||
if (value && !isValidVariableName(value)) {
|
||||
return Promise.reject(
|
||||
new Error(t("node.invalidVariableName"))
|
||||
@@ -1266,6 +1319,11 @@ const NodeInspector: FC = () => {
|
||||
{ required, message: t("fieldRequired", { field: desc }) },
|
||||
{
|
||||
validator(_, value) {
|
||||
if (value && usingVars && !usingVars[value]) {
|
||||
return Promise.reject(
|
||||
new Error(t("node.undefinedVariable", { variable: value }))
|
||||
);
|
||||
}
|
||||
if (value && !isValidVariableName(value)) {
|
||||
return Promise.reject(new Error(t("node.invalidVariableName")));
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import G6 from "@antv/g6";
|
||||
import { useWorkspace } from "../contexts/workspace-context";
|
||||
import { TreeGraphData, getNodeType } from "../misc/b3type";
|
||||
import { checkTreeData, nodeDefs, usingGroups } from "../misc/b3util";
|
||||
import { TreeGraphData, getNodeType, isExprType } from "../misc/b3type";
|
||||
import { checkTreeData, nodeDefs, parseExpr, usingGroups, usingVars } from "../misc/b3util";
|
||||
import i18n from "../misc/i18n";
|
||||
import { isMacos } from "../misc/keys";
|
||||
|
||||
@@ -145,11 +145,20 @@ G6.registerNode(
|
||||
const nodeDef = nodeDefs.get(data.name);
|
||||
let classify = getNodeType(nodeDef);
|
||||
let color = nodeDef.color || NODE_COLORS[classify] || NODE_COLORS["Other"];
|
||||
|
||||
if (
|
||||
!nodeDefs.has(data.name) ||
|
||||
(data.path && !data.children?.length) ||
|
||||
!checkTreeData(data) ||
|
||||
(nodeDef.group && !usingGroups[nodeDef.group])
|
||||
(nodeDef.group && usingGroups && !usingGroups[nodeDef.group]) ||
|
||||
(usingVars && data.input?.some((v) => !usingVars![v])) ||
|
||||
(usingVars && data.output?.some((v) => !usingVars![v])) ||
|
||||
(usingVars &&
|
||||
nodeDef.args?.some(
|
||||
(v) =>
|
||||
isExprType(v.type) &&
|
||||
parseExpr(data.args?.[v.name] ?? "").some((vv) => !usingVars![vv])
|
||||
))
|
||||
) {
|
||||
classify = "Error";
|
||||
color = NODE_COLORS[classify];
|
||||
|
||||
+21
-20
@@ -26,10 +26,10 @@ export class NodeDefs extends Map<string, NodeDef> {
|
||||
|
||||
export let nodeDefs: NodeDefs = new NodeDefs();
|
||||
export let groupDefs: string[] = [];
|
||||
export let usingGroups: Record<string, boolean> | null = null;
|
||||
export let usingVars: Record<string, VarDef> | null = null;
|
||||
|
||||
export let usingGroups: Record<string, boolean> = {};
|
||||
export let usingVars: Record<string, boolean> = {};
|
||||
|
||||
const parsedExprs: Record<string, string[]> = {};
|
||||
let workdir: string = "";
|
||||
let alertError: (msg: string, duration?: number) => void = () => {};
|
||||
|
||||
@@ -58,16 +58,18 @@ export const initWorkdir = (path: string, handler: typeof alertError) => {
|
||||
};
|
||||
|
||||
export const updateUsingGroups = (group: string[]) => {
|
||||
usingGroups = {};
|
||||
usingGroups = null;
|
||||
for (const g of group) {
|
||||
usingGroups ??= {};
|
||||
usingGroups[g] = true;
|
||||
}
|
||||
};
|
||||
|
||||
export const updateUsingVars = (vars: VarDef[]) => {
|
||||
usingVars = {};
|
||||
usingVars = null;
|
||||
for (const v of vars) {
|
||||
usingVars[v.name] = true;
|
||||
usingVars ??= {};
|
||||
usingVars[v.name] = v;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -711,6 +713,7 @@ export const isTreeFile = (path: string) => {
|
||||
};
|
||||
|
||||
export const loadVarDef = (list: ImportDef[]) => {
|
||||
const all: Set<VarDef> = new Set();
|
||||
for (const entry of list) {
|
||||
// TODO: check file date
|
||||
// TODO: use cache
|
||||
@@ -743,7 +746,9 @@ export const loadVarDef = (list: ImportDef[]) => {
|
||||
return false;
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name));
|
||||
entry.vars.forEach((v) => all.add(v));
|
||||
}
|
||||
return Array.from(all);
|
||||
};
|
||||
|
||||
export const loadSubtreeVarDef = (data: TreeGraphData) => {
|
||||
@@ -757,21 +762,17 @@ export const loadSubtreeVarDef = (data: TreeGraphData) => {
|
||||
}
|
||||
};
|
||||
traverse(data);
|
||||
loadVarDef(list);
|
||||
const filter: Map<string, boolean> = new Map();
|
||||
const result: VarDef[] = [];
|
||||
list.forEach((entry) => {
|
||||
entry.vars.forEach((v) => {
|
||||
if (!filter.has(v.name)) {
|
||||
filter.set(v.name, true);
|
||||
result.push(v);
|
||||
}
|
||||
});
|
||||
});
|
||||
return result;
|
||||
return loadVarDef(list);
|
||||
};
|
||||
|
||||
export const parseExpr = (expr: string) => {
|
||||
const result = expr.split(/[^a-zA-Z0-9_.]/);
|
||||
return result.map((v) => v.split(".")[0]).filter((v) => v);
|
||||
if (parsedExprs[expr]) {
|
||||
return parsedExprs[expr];
|
||||
}
|
||||
const result = expr
|
||||
.split(/[^a-zA-Z0-9_.]/)
|
||||
.map((v) => v.split(".")[0])
|
||||
.filter((v) => isValidVariableName(v));
|
||||
parsedExprs[expr] = result;
|
||||
return result;
|
||||
};
|
||||
Reference in new issue
Block a user