support array type for node args
This commit is contained in:
1 parent
d7a2ce0f9b
commit
f2d0686707
9 files changed
+676
-268
No files matched your search
+120
-59
@@ -1,11 +1,11 @@
|
||||
import { useWorkspace } from "@/contexts/workspace-context";
|
||||
import { NodeArg, NodeDef, NodeModel, TreeGraphData, TreeModel } from "@/misc/b3type";
|
||||
import i18n from "@/misc/i18n";
|
||||
import * as fs from "fs";
|
||||
import { message } from "./hooks";
|
||||
import { isMacos } from "./keys";
|
||||
import Path from "./path";
|
||||
import { zhNodeDef } from "./template";
|
||||
import i18n from "@/misc/i18n";
|
||||
import { isMacos } from "./keys";
|
||||
|
||||
let ctx: CanvasRenderingContext2D | null = null;
|
||||
let defaultFontSize = "";
|
||||
@@ -110,7 +110,86 @@ const error = (data: NodeModel | TreeGraphData, msg: string) => {
|
||||
console.error(`check ${data.id}|${data.name}: ${msg}`);
|
||||
};
|
||||
|
||||
const checkNodeArg = (
|
||||
export const getNodeArgRawType = (arg: NodeArg) => {
|
||||
return arg.type.match(/^\w+/)![0] as NodeArg["type"];
|
||||
};
|
||||
|
||||
export const isNodeArgArray = (arg: NodeArg) => {
|
||||
return arg.type.includes("[]");
|
||||
};
|
||||
|
||||
export const isNodeArgOptional = (arg: NodeArg) => {
|
||||
return arg.type.includes("?");
|
||||
};
|
||||
|
||||
export const checkNodeArgValue = (
|
||||
data: NodeModel | TreeGraphData,
|
||||
arg: NodeArg,
|
||||
value: unknown,
|
||||
verbose?: boolean
|
||||
) => {
|
||||
let hasError = false;
|
||||
const type = getNodeArgRawType(arg);
|
||||
if (type === "float") {
|
||||
const isNumber = typeof value === "number";
|
||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||
if (!(isNumber || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a number`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "int") {
|
||||
const isInt = typeof value === "number" && value === Math.floor(value);
|
||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||
if (!(isInt || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a int`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "string") {
|
||||
const isString = typeof value === "string" && value;
|
||||
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
||||
if (!(isString || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a string`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "enum") {
|
||||
const isEnum = !!arg.options?.find((option) => option.value === value);
|
||||
const isOptional = value === undefined && isNodeArgOptional(arg);
|
||||
if (!(isEnum || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a one of the option values`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "code") {
|
||||
const isCode = typeof value === "string" && value;
|
||||
const isOptional = (value === undefined || value === "") && isNodeArgOptional(arg);
|
||||
if (!(isCode || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${JSON.stringify(value)}' is not a string`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "json") {
|
||||
const isJson = value !== undefined && value !== "";
|
||||
const isOptional = isNodeArgOptional(arg);
|
||||
if (!(isJson || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not an invalid object`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
|
||||
return !hasError;
|
||||
};
|
||||
|
||||
export const checkNodeArg = (
|
||||
data: NodeModel | TreeGraphData,
|
||||
conf: NodeDef,
|
||||
i: number,
|
||||
@@ -118,62 +197,22 @@ const checkNodeArg = (
|
||||
) => {
|
||||
let hasError = false;
|
||||
const arg = conf.args![i] as NodeArg;
|
||||
const type = arg.type.replace("?", "") as NodeArg["type"];
|
||||
const value = data.args?.[arg.name];
|
||||
if (type === "float") {
|
||||
const isNumber = typeof value === "number";
|
||||
const isOptional = value === undefined && arg.type === "float?";
|
||||
if (!(isNumber || isOptional)) {
|
||||
if (isNodeArgArray(arg)) {
|
||||
if (!Array.isArray(value)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not a number`);
|
||||
error(data, `'${arg.name}=${value}' is not an array`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "int") {
|
||||
const isInt = typeof value === "number" && value === Math.floor(value);
|
||||
const isOptional = value === undefined && arg.type === "int?";
|
||||
if (!(isInt || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not a int`);
|
||||
} else {
|
||||
for (let j = 0; j < value.length; j++) {
|
||||
if (!checkNodeArgValue(data, arg, value[j], verbose)) {
|
||||
hasError = true;
|
||||
}
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "string") {
|
||||
const isString = typeof value === "string" && value;
|
||||
const isOptional = (value === undefined || value === "") && arg.type === "string?";
|
||||
if (!(isString || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not a string`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "enum") {
|
||||
const isEnum = !!arg.options?.find((option) => option.value === value);
|
||||
const isOptional = value === undefined && arg.type === "enum?";
|
||||
if (!(isEnum || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not a one of the option values`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "code") {
|
||||
const isCode = typeof value === "string" && value;
|
||||
const isOptional = (value === undefined || value === "") && arg.type === "code?";
|
||||
if (!(isCode || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not a string`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (type === "json") {
|
||||
const isJson = value !== undefined && value !== "";
|
||||
const isOptional = arg.type === "json?";
|
||||
if (!(isJson || isOptional)) {
|
||||
if (verbose) {
|
||||
error(data, `'${arg.name}=${value}' is not an invalid object`);
|
||||
}
|
||||
hasError = true;
|
||||
}
|
||||
} else if (!checkNodeArgValue(data, arg, value, verbose)) {
|
||||
hasError = true;
|
||||
}
|
||||
if (arg.oneof !== undefined) {
|
||||
const idx = conf.input?.findIndex((v) => v.startsWith(arg.oneof!)) ?? -1;
|
||||
@@ -210,6 +249,8 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
||||
error(data, `expect ${conf.children} children, but got ${count}`);
|
||||
}
|
||||
}
|
||||
|
||||
let hasVaridicInput = false;
|
||||
if (conf.input) {
|
||||
for (let i = 0; i < conf.input.length; i++) {
|
||||
if (!data.input) {
|
||||
@@ -218,15 +259,20 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
||||
if (!data.input[i]) {
|
||||
data.input[i] = "";
|
||||
}
|
||||
if (conf.input[i].indexOf("?") === -1 && !data.input[i]) {
|
||||
if (!isValidInputOrOutput(conf.input, data.input, i)) {
|
||||
error(data, `intput field '${conf.input[i]}' is required`);
|
||||
hasError = true;
|
||||
}
|
||||
if (i === conf.input.length - 1 && conf.input.at(-1)?.endsWith("...")) {
|
||||
hasVaridicInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.input) {
|
||||
if (data.input && !hasVaridicInput) {
|
||||
data.input.length = conf.input?.length || 0;
|
||||
}
|
||||
|
||||
let hasVaridicOutput = false;
|
||||
if (conf.output) {
|
||||
for (let i = 0; i < conf.output.length; i++) {
|
||||
if (!data.output) {
|
||||
@@ -235,13 +281,16 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
||||
if (!data.output[i]) {
|
||||
data.output[i] = "";
|
||||
}
|
||||
if (conf.output[i].indexOf("?") === -1 && !data.output[i]) {
|
||||
if (!isValidInputOrOutput(conf.output, data.output, i)) {
|
||||
error(data, `output field '${conf.output[i]}' is required`);
|
||||
hasError = true;
|
||||
}
|
||||
if (i === conf.output.length - 1 && conf.output.at(-1)?.endsWith("...")) {
|
||||
hasVaridicOutput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (data.output) {
|
||||
if (data.output && !hasVaridicOutput) {
|
||||
data.output.length = conf.output?.length || 0;
|
||||
}
|
||||
if (conf.args) {
|
||||
@@ -456,18 +505,30 @@ export const checkChildrenLimit = (data: TreeGraphData) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const isValidInputOrOutput = (
|
||||
inputDef: string[],
|
||||
inputData: string[] | undefined,
|
||||
index: number
|
||||
) => {
|
||||
return (
|
||||
inputDef[index].includes("?") ||
|
||||
inputData?.[index] ||
|
||||
(index === inputDef.length - 1 && inputDef[index].endsWith("..."))
|
||||
);
|
||||
};
|
||||
|
||||
export const checkTreeData = (data: TreeGraphData) => {
|
||||
const conf = data.def;
|
||||
if (conf.input) {
|
||||
for (let i = 0; i < conf.input.length; i++) {
|
||||
if (conf.input[i].indexOf("?") === -1 && !data.input?.[i]) {
|
||||
if (!isValidInputOrOutput(conf.input, data.input, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (conf.output) {
|
||||
for (let i = 0; i < conf.output.length; i++) {
|
||||
if (conf.output[i].indexOf("?") === -1 && !data.output?.[i]) {
|
||||
if (!isValidInputOrOutput(conf.output, data.output, i)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+50
-37
@@ -32,34 +32,6 @@ export const zhNodeDef = () => {
|
||||
],
|
||||
doc: "+ 只能有一个子节点,多个仅执行第一个\n+ 当子节点返回 `failure` 时,抛出异常\n+ 其余情况返回子节点的执行状态\n",
|
||||
},
|
||||
{
|
||||
name: "Assign",
|
||||
type: "Action",
|
||||
children: 0,
|
||||
status: ["success"],
|
||||
desc: "对输入对象设置 key 和 value",
|
||||
input: ["输入对象", "输入key?", "输入value?"],
|
||||
args: [
|
||||
{
|
||||
name: "key",
|
||||
type: "string?",
|
||||
desc: "常量key",
|
||||
oneof: "输入key",
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
type: "json?",
|
||||
desc: "常量value",
|
||||
oneof: "输入value",
|
||||
},
|
||||
{
|
||||
name: "underscore",
|
||||
type: "boolean?",
|
||||
desc: "允许下划线",
|
||||
},
|
||||
],
|
||||
doc: "+ 对输入对象设置 `key` 和 `value`\n+ 输入参数1必须为对象,否则返回 `failure`\n+ 如果 `key` 为 `undefined`, 也返回 `failure`\n+ 如果 `value` 为 `undefined` 或 `null`, 则删除 `key` 的值\n",
|
||||
},
|
||||
{
|
||||
name: "Calculate",
|
||||
type: "Action",
|
||||
@@ -138,6 +110,24 @@ export const zhNodeDef = () => {
|
||||
output: ["变量"],
|
||||
doc: "+ 只能有一个子节点,多个仅执行第一个\n+ 遍历输入数组,将当前元素写入`变量`\n+ 当子节点返回 `failure` 时,退出遍历并返回 `failure` 状态\n+ 执行完所有子节点后,返回 `success`",
|
||||
},
|
||||
{
|
||||
name: "GetField",
|
||||
type: "Action",
|
||||
children: 0,
|
||||
status: ["success", "failure"],
|
||||
desc: "获取对象的字段值",
|
||||
args: [
|
||||
{
|
||||
name: "field",
|
||||
type: "string?",
|
||||
desc: "字段(field)",
|
||||
oneof: "字段(field)",
|
||||
},
|
||||
],
|
||||
input: ["对象", "字段(field)?"],
|
||||
output: ["字段值(value)"],
|
||||
doc: "+ 合法元素不包括 `undefined` 和 `null`\n+ 只有获取到合法元素时候才会返回 `success`,否则返回 `failure`\n",
|
||||
},
|
||||
{
|
||||
name: "IfElse",
|
||||
type: "Composite",
|
||||
@@ -160,18 +150,18 @@ export const zhNodeDef = () => {
|
||||
type: "Action",
|
||||
children: 0,
|
||||
status: ["success", "failure"],
|
||||
desc: "索引输入的数组或对象",
|
||||
desc: "索引输入的数组",
|
||||
args: [
|
||||
{
|
||||
name: "idx",
|
||||
type: "string?",
|
||||
name: "index",
|
||||
type: "int?",
|
||||
desc: "索引",
|
||||
oneof: "索引",
|
||||
},
|
||||
],
|
||||
input: ["输入目标", "索引?"],
|
||||
output: ["输出目标"],
|
||||
doc: "+ 合法元素不包括 `undefined` 和 `null`\n+ 只有索引到有合法元素时候才会返回 `success`,否则返回 `failure`\n",
|
||||
input: ["数组", "索引?"],
|
||||
output: ["值"],
|
||||
doc: "+ 合法元素不包括 `undefined` 和 `null`\n+ 索引数组的时候,第一个元素的索引为 0\n+ 只有索引到有合法元素时候才会返回 `success`,否则返回 `failure`\n",
|
||||
},
|
||||
{
|
||||
name: "Invert",
|
||||
@@ -198,14 +188,14 @@ export const zhNodeDef = () => {
|
||||
input: ["已存在变量名?"],
|
||||
args: [
|
||||
{
|
||||
name: "expr",
|
||||
name: "value",
|
||||
type: "json?",
|
||||
desc: "表达式",
|
||||
desc: "值(value)",
|
||||
oneof: "已存在变量名",
|
||||
},
|
||||
],
|
||||
output: ["新变量名"],
|
||||
doc: "+ 如果有输入变量,则给已有变量重新定义一个名字\n+ 如果有表达式,则使用表达式\n+ 如果表达式为 `null`,则清除变量\n",
|
||||
doc: "+ 如果有输入变量,则给已有变量重新定义一个名字\n+ 如果`值(value)`为 `null`,则清除变量\n",
|
||||
},
|
||||
{
|
||||
name: "Listen",
|
||||
@@ -441,6 +431,29 @@ export const zhNodeDef = () => {
|
||||
desc: "顺序执行",
|
||||
doc: "+ 一直往下执行,只有当所有子节点都返回 `success`, 才返回 `success`\n+ 若子节点返回 `failure`,则直接返回 `failure` 状态\n+ 其余情况返回 `running` 状态\n",
|
||||
},
|
||||
{
|
||||
name: "SetField",
|
||||
type: "Action",
|
||||
children: 0,
|
||||
status: ["success", "failure"],
|
||||
desc: "设置对象字段值",
|
||||
input: ["输入对象", "字段(field)?", "值(value)?"],
|
||||
args: [
|
||||
{
|
||||
name: "field",
|
||||
type: "string?",
|
||||
desc: "字段(field)",
|
||||
oneof: "字段(field)",
|
||||
},
|
||||
{
|
||||
name: "value",
|
||||
type: "json?",
|
||||
desc: "值(value)",
|
||||
oneof: "值(value)",
|
||||
},
|
||||
],
|
||||
doc: "+ 对输入对象设置 `field` 和 `value`\n+ 输入参数1必须为对象,否则返回 `failure`\n+ 如果 `field` 不为 `string`, 也返回 `failure`\n+ 如果 `value` 为 `undefined` 或 `null`, 则删除 `field` 的值\n",
|
||||
},
|
||||
{
|
||||
name: "Timeout",
|
||||
type: "Decorator",
|
||||
|
||||
Reference in new issue
Block a user