support array type for node args

This commit is contained in:
codetypes committed 2024-09-08 10:41:58 +08:00
1 parent d7a2ce0f9b
commit f2d0686707
9 files changed
+676 -268

No files matched your search

+120 -59
View File
@@ -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
View File
@@ -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",