support array type for node args
This commit is contained in:
1 parent
d7a2ce0f9b
commit
f2d0686707
9 files changed
+545
-137
No files matched your search
@@ -6,6 +6,9 @@
|
|||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
},
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +83,9 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -83,6 +83,9 @@
|
|||||||
"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": "调试",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import Path from "@/misc/path";
|
|||||||
import { DownOutlined } from "@ant-design/icons";
|
import { DownOutlined } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Flex, FlexProps, Input, MenuProps, Space, Tree } from "antd";
|
import { Button, Dropdown, Flex, FlexProps, Input, MenuProps, Space, Tree } from "antd";
|
||||||
import { ItemType } from "antd/es/menu/interface";
|
import { ItemType } from "antd/es/menu/interface";
|
||||||
import { ipcRenderer, shell } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import React, { FC, useEffect, useMemo, useState } from "react";
|
import React, { FC, useEffect, useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|||||||
+364
-38
@@ -1,9 +1,15 @@
|
|||||||
import { EditNode, EditTree, useWorkspace } from "@/contexts/workspace-context";
|
import { EditNode, EditTree, useWorkspace } from "@/contexts/workspace-context";
|
||||||
import { NodeArg, NodeModel, TreeGraphData } from "@/misc/b3type";
|
import { NodeModel, TreeGraphData } from "@/misc/b3type";
|
||||||
import { checkOneof } from "@/misc/b3util";
|
import {
|
||||||
|
checkNodeArgValue,
|
||||||
|
checkOneof,
|
||||||
|
getNodeArgRawType,
|
||||||
|
isNodeArgArray,
|
||||||
|
isNodeArgOptional,
|
||||||
|
} 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";
|
||||||
import { EditOutlined } from "@ant-design/icons";
|
import { EditOutlined, MinusCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
import {
|
import {
|
||||||
AutoComplete,
|
AutoComplete,
|
||||||
Button,
|
Button,
|
||||||
@@ -20,6 +26,7 @@ import { DefaultOptionType } from "antd/es/select";
|
|||||||
import { FC, useEffect, useMemo } from "react";
|
import { FC, useEffect, useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
import { useDebounceCallback } from "usehooks-ts";
|
||||||
|
|
||||||
interface OptionType extends DefaultOptionType {
|
interface OptionType extends DefaultOptionType {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -91,6 +98,15 @@ const NodeInspector: FC = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const isVariadic = (arr: string[], i: number) => {
|
||||||
|
return arr[i].endsWith("...") && i === arr.length - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFieldsLater = useDebounceCallback(
|
||||||
|
() => form.validateFields({ recursive: true }),
|
||||||
|
100
|
||||||
|
);
|
||||||
|
|
||||||
// set form values
|
// set form values
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const data = workspace.editingNode.data;
|
const data = workspace.editingNode.data;
|
||||||
@@ -108,24 +124,45 @@ const NodeInspector: FC = () => {
|
|||||||
} else {
|
} else {
|
||||||
form.setFieldValue("children", def.children);
|
form.setFieldValue("children", def.children);
|
||||||
}
|
}
|
||||||
def.args?.forEach((v) => {
|
def.args?.forEach((arg) => {
|
||||||
if (v.type === "json" || v.type === "json?") {
|
const type = getNodeArgRawType(arg);
|
||||||
const value = data.args?.[v.name];
|
const value = data.args?.[arg.name];
|
||||||
|
const name = `args.${arg.name}`;
|
||||||
|
if (isNodeArgArray(arg)) {
|
||||||
form.setFieldValue(
|
form.setFieldValue(
|
||||||
`args.${v.name}`,
|
name,
|
||||||
value === null ? "null" : JSON.stringify(value ?? v.default, null, 2)
|
(Array.isArray(value) ? value : []).map((item) => {
|
||||||
|
if (type === "json") {
|
||||||
|
return item === null ? "null" : JSON.stringify(item ?? arg.default, null, 2);
|
||||||
|
} else {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} else if (type === "json") {
|
||||||
|
form.setFieldValue(
|
||||||
|
name,
|
||||||
|
value === null ? "null" : JSON.stringify(value ?? arg.default, null, 2)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
form.setFieldValue(`args.${v.name}`, data.args?.[v.name] ?? v.default);
|
form.setFieldValue(name, value ?? arg.default);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
def.input?.forEach((_, i) => {
|
def.input?.forEach((_, i) => {
|
||||||
|
if (isVariadic(def.input!, i)) {
|
||||||
|
form.setFieldValue(`input.${i}`, data.input?.slice(i) ?? []);
|
||||||
|
} else {
|
||||||
form.setFieldValue(`input.${i}`, data.input?.[i]);
|
form.setFieldValue(`input.${i}`, data.input?.[i]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
def.output?.forEach((_, i) => {
|
def.output?.forEach((_, i) => {
|
||||||
|
if (isVariadic(def.output!, i)) {
|
||||||
|
form.setFieldValue(`output.${i}`, data.output?.slice(i) ?? []);
|
||||||
|
} else {
|
||||||
form.setFieldValue(`output.${i}`, data.output?.[i]);
|
form.setFieldValue(`output.${i}`, data.output?.[i]);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
form.validateFields();
|
validateFieldsLater();
|
||||||
}, [workspace.editingNode]);
|
}, [workspace.editingNode]);
|
||||||
|
|
||||||
// auto complete for node
|
// auto complete for node
|
||||||
@@ -143,13 +180,25 @@ const NodeInspector: FC = () => {
|
|||||||
const collect = (node?: TreeGraphData) => {
|
const collect = (node?: TreeGraphData) => {
|
||||||
if (node) {
|
if (node) {
|
||||||
node.input?.forEach((v, i) => {
|
node.input?.forEach((v, i) => {
|
||||||
const desc = node.def.input?.[i] ?? "<unknown>";
|
let desc: string;
|
||||||
|
const inputDef = node.def.input;
|
||||||
|
if (inputDef && i >= inputDef.length && isVariadic(inputDef, inputDef.length - 1)) {
|
||||||
|
desc = inputDef[inputDef.length - 1];
|
||||||
|
} else {
|
||||||
|
desc = inputDef?.[i] ?? "<unknown>";
|
||||||
|
}
|
||||||
if (v && !options.find((option) => option.value === v)) {
|
if (v && !options.find((option) => option.value === v)) {
|
||||||
options.push({ label: `${v}(${desc})`, value: v });
|
options.push({ label: `${v}(${desc})`, value: v });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
node.output?.forEach((v, i) => {
|
node.output?.forEach((v, i) => {
|
||||||
const desc = node.def.output?.[i] ?? "<unknown>";
|
let desc: string;
|
||||||
|
const outputDef = node.def.output;
|
||||||
|
if (outputDef && i >= outputDef.length && isVariadic(outputDef, outputDef.length - 1)) {
|
||||||
|
desc = outputDef[outputDef.length - 1];
|
||||||
|
} else {
|
||||||
|
desc = outputDef?.[i] ?? "<unknown>";
|
||||||
|
}
|
||||||
if (v && !options.find((option) => option.value === v)) {
|
if (v && !options.find((option) => option.value === v)) {
|
||||||
options.push({ label: `${v}(${desc})`, value: v });
|
options.push({ label: `${v}(${desc})`, value: v });
|
||||||
}
|
}
|
||||||
@@ -192,13 +241,34 @@ const NodeInspector: FC = () => {
|
|||||||
|
|
||||||
if (def.args?.length) {
|
if (def.args?.length) {
|
||||||
def.args?.forEach((arg) => {
|
def.args?.forEach((arg) => {
|
||||||
const v = values[`args.${arg.name}`];
|
const value = values[`args.${arg.name}`];
|
||||||
if (v !== null && v !== undefined && v !== "") {
|
if (value !== null && value !== undefined && value !== "") {
|
||||||
data.args ||= {};
|
data.args ||= {};
|
||||||
if (arg.type === "json" || arg.type === "json?") {
|
const type = getNodeArgRawType(arg);
|
||||||
data.args[arg.name] = v === "null" ? null : JSON.parse(v);
|
if (isNodeArgArray(arg)) {
|
||||||
|
const arr: unknown[] = [];
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((item) => {
|
||||||
|
if (type === "json") {
|
||||||
|
try {
|
||||||
|
arr.push(item === "null" ? null : JSON.parse(item));
|
||||||
|
} catch {
|
||||||
|
/** ignore */
|
||||||
|
}
|
||||||
|
} else if (item !== null && item !== undefined) {
|
||||||
|
arr.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
data.args[arg.name] = arr;
|
||||||
|
} else if (type === "json") {
|
||||||
|
try {
|
||||||
|
data.args[arg.name] = value === "null" ? null : JSON.parse(value);
|
||||||
|
} catch {
|
||||||
|
/** ignore */
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
data.args[arg.name] = v;
|
data.args[arg.name] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -208,9 +278,14 @@ const NodeInspector: FC = () => {
|
|||||||
|
|
||||||
if (def.input?.length) {
|
if (def.input?.length) {
|
||||||
def.input?.forEach((_, i) => {
|
def.input?.forEach((_, i) => {
|
||||||
const v = values[`input.${i}`];
|
|
||||||
data.input ||= [];
|
data.input ||= [];
|
||||||
|
if (isVariadic(def.input!, i)) {
|
||||||
|
const arr = (values[`input.${i}`] ?? []) as string[];
|
||||||
|
data.input.push(...arr.filter((v) => typeof v === "string"));
|
||||||
|
} else {
|
||||||
|
const v = values[`input.${i}`];
|
||||||
data.input.push(v ?? "");
|
data.input.push(v ?? "");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
data.input = [];
|
data.input = [];
|
||||||
@@ -218,9 +293,14 @@ const NodeInspector: FC = () => {
|
|||||||
|
|
||||||
if (def.output?.length) {
|
if (def.output?.length) {
|
||||||
def.output?.forEach((_, i) => {
|
def.output?.forEach((_, i) => {
|
||||||
const v = values[`output.${i}`];
|
|
||||||
data.output ||= [];
|
data.output ||= [];
|
||||||
|
if (isVariadic(def.output!, i)) {
|
||||||
|
const arr = (values[`output.${i}`] ?? []) as string[];
|
||||||
|
data.output.push(...arr.filter((v) => typeof v === "string"));
|
||||||
|
} else {
|
||||||
|
const v = values[`output.${i}`];
|
||||||
data.output.push(v ?? "");
|
data.output.push(v ?? "");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
data.output = [];
|
data.output = [];
|
||||||
@@ -328,6 +408,68 @@ const NodeInspector: FC = () => {
|
|||||||
{def.input.map((v, i) => {
|
{def.input.map((v, i) => {
|
||||||
const required = v.indexOf("?") === -1;
|
const required = v.indexOf("?") === -1;
|
||||||
const desc = v.replace("?", "");
|
const desc = v.replace("?", "");
|
||||||
|
if (isVariadic(def.input!, i)) {
|
||||||
|
return (
|
||||||
|
<Form.Item label={desc} key={`input.${i}`}>
|
||||||
|
<Form.List name={`input.${i}`}>
|
||||||
|
{(fields, { add, remove }, { errors }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
rowGap: 0,
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<Flex key={field.key} gap={4}>
|
||||||
|
<Form.Item
|
||||||
|
name={field.name}
|
||||||
|
validateTrigger={["onChange", "onBlur"]}
|
||||||
|
style={{ width: "100%", marginBottom: 5 }}
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
disabled={disabled}
|
||||||
|
options={inoutVarOptions}
|
||||||
|
onBlur={form.submit}
|
||||||
|
onInputKeyDown={(e) =>
|
||||||
|
e.code === Hotkey.Escape && e.preventDefault()
|
||||||
|
}
|
||||||
|
filterOption={(inputValue: string, option?: OptionType) => {
|
||||||
|
const label = option!.label as string;
|
||||||
|
return (
|
||||||
|
label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginBottom: "6px" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add("");
|
||||||
|
}}
|
||||||
|
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t("node.input.add")}
|
||||||
|
</Button>
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={desc}
|
label={desc}
|
||||||
@@ -378,6 +520,7 @@ const NodeInspector: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -386,21 +529,137 @@ const NodeInspector: FC = () => {
|
|||||||
<Divider orientation="left">
|
<Divider orientation="left">
|
||||||
<h4>{t("node.args")}</h4>
|
<h4>{t("node.args")}</h4>
|
||||||
</Divider>
|
</Divider>
|
||||||
{def.args.map((v) => {
|
{def.args.map((arg) => {
|
||||||
const required = v.type.indexOf("?") === -1;
|
const required = !isNodeArgOptional(arg);
|
||||||
const type = v.type.replace("?", "") as NodeArg["type"];
|
const type = getNodeArgRawType(arg);
|
||||||
|
if (isNodeArgArray(arg)) {
|
||||||
return (
|
return (
|
||||||
|
<Form.Item key={`args.${arg.name}`}>
|
||||||
|
<Form.List name={`args.${arg.name}`}>
|
||||||
|
{(items, { add, remove }, { errors }) => (
|
||||||
|
<div style={{ display: "flex", rowGap: 0, flexDirection: "column" }}>
|
||||||
|
{items.map((item, idx) => (
|
||||||
|
<Flex key={item.key} gap={4}>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={`args.${v.name}`}
|
name={item.name}
|
||||||
label={v.desc}
|
label={idx === 0 ? `${arg.desc}[${idx}]` : `[${idx}]`}
|
||||||
key={`args.${v.name}`}
|
validateTrigger={["onChange", "onBlur"]}
|
||||||
initialValue={type === "boolean" ? v.default ?? false : v.default}
|
style={{ width: "100%", marginBottom: 5 }}
|
||||||
|
initialValue={
|
||||||
|
type === "boolean" ? arg.default ?? false : arg.default
|
||||||
|
}
|
||||||
valuePropName={type === "boolean" ? "checked" : undefined}
|
valuePropName={type === "boolean" ? "checked" : undefined}
|
||||||
rules={[
|
rules={[
|
||||||
{ required, message: t("node.fileRequired", { field: v.desc }) },
|
() => ({
|
||||||
|
validator(_, value) {
|
||||||
|
if (type === "json") {
|
||||||
|
try {
|
||||||
|
if (value !== "null") {
|
||||||
|
JSON.parse(value);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(t("node.invalidValue"))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
!checkNodeArgValue(editingNode.data, arg, value, true)
|
||||||
|
) {
|
||||||
|
return Promise.reject(new Error(t("node.invalidValue")));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{type === "string" && (
|
||||||
|
<TextArea autoSize disabled={disabled} onBlur={form.submit} />
|
||||||
|
)}
|
||||||
|
{type === "json" && (
|
||||||
|
<TextArea autoSize disabled={disabled} onBlur={form.submit} />
|
||||||
|
)}
|
||||||
|
{type === "int" && (
|
||||||
|
<InputNumber
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={form.submit}
|
||||||
|
precision={0}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{type === "float" && (
|
||||||
|
<InputNumber disabled={disabled} onBlur={form.submit} />
|
||||||
|
)}
|
||||||
|
{type === "boolean" && (
|
||||||
|
<Switch disabled={disabled} onChange={form.submit} />
|
||||||
|
)}
|
||||||
|
{type === "code" && (
|
||||||
|
<Input disabled={disabled} onBlur={form.submit} />
|
||||||
|
)}
|
||||||
|
{type === "enum" && (
|
||||||
|
<Select
|
||||||
|
disabled={disabled}
|
||||||
|
onBlur={form.submit}
|
||||||
|
onChange={form.submit}
|
||||||
|
>
|
||||||
|
{arg.options?.map((value, i) => {
|
||||||
|
return (
|
||||||
|
<Select.Option key={i} value={value.value}>
|
||||||
|
{value.name}
|
||||||
|
</Select.Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginBottom: "6px" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(item.name);
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
<Form.Item
|
||||||
|
label={items.length === 0 ? arg.desc : undefined}
|
||||||
|
style={{
|
||||||
|
marginLeft: items.length === 0 ? undefined : "100px",
|
||||||
|
marginRight: items.length === 0 ? undefined : "18px",
|
||||||
|
alignItems: "end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add(arg.default ?? type === "boolean" ? false : "");
|
||||||
|
if (type === "boolean") {
|
||||||
|
form.submit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t("node.args.add")}
|
||||||
|
</Button>
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
name={`args.${arg.name}`}
|
||||||
|
key={`args.${arg.name}`}
|
||||||
|
label={arg.desc}
|
||||||
|
initialValue={type === "boolean" ? arg.default ?? false : arg.default}
|
||||||
|
valuePropName={type === "boolean" ? "checked" : undefined}
|
||||||
|
rules={[
|
||||||
|
{ required, message: t("node.fileRequired", { field: arg.desc }) },
|
||||||
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
({ getFieldValue, setFieldValue, isFieldValidating, validateFields }) => ({
|
||||||
validator(_, value) {
|
validator(_, value) {
|
||||||
if (value && (v.type === "json" || v.type === "json?")) {
|
if (value && type === "json") {
|
||||||
try {
|
try {
|
||||||
if (value !== "null") {
|
if (value !== "null") {
|
||||||
JSON.parse(value);
|
JSON.parse(value);
|
||||||
@@ -409,20 +668,23 @@ const NodeInspector: FC = () => {
|
|||||||
return Promise.reject(new Error(t("node.invalidValue")));
|
return Promise.reject(new Error(t("node.invalidValue")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!v.oneof) {
|
if (!checkNodeArgValue(editingNode.data, arg, value)) {
|
||||||
|
return Promise.reject(new Error(t("node.invalidValue")));
|
||||||
|
}
|
||||||
|
if (!arg.oneof) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
const idx = def.input?.findIndex(
|
const idx = def.input?.findIndex(
|
||||||
(input) => input.replace("?", "") === v.oneof
|
(input) => input.replace("?", "") === arg.oneof
|
||||||
);
|
);
|
||||||
if (idx === undefined || idx < 0) {
|
if (idx === undefined || idx < 0) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(t("node.oneof.inputNotfound", { input: v.oneof }))
|
new Error(t("node.oneof.inputNotfound", { input: arg.oneof }))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const inputName = `input.${idx}`;
|
const inputName = `input.${idx}`;
|
||||||
if (!isFieldValidating(inputName)) {
|
if (!isFieldValidating(inputName)) {
|
||||||
setFieldValue(`args.${v.name}`, value);
|
setFieldValue(`args.${arg.name}`, value);
|
||||||
validateFields([inputName]);
|
validateFields([inputName]);
|
||||||
}
|
}
|
||||||
if (!checkOneof(getFieldValue(inputName) ?? "", value)) {
|
if (!checkOneof(getFieldValue(inputName) ?? "", value)) {
|
||||||
@@ -430,8 +692,8 @@ const NodeInspector: FC = () => {
|
|||||||
new Error(
|
new Error(
|
||||||
t("node.oneof.error", {
|
t("node.oneof.error", {
|
||||||
input: def.input![idx],
|
input: def.input![idx],
|
||||||
arg: v.name,
|
arg: arg.name,
|
||||||
desc: v.desc ?? "",
|
desc: arg.desc ?? "",
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -456,7 +718,7 @@ const NodeInspector: FC = () => {
|
|||||||
{type === "code" && <Input disabled={disabled} onBlur={form.submit} />}
|
{type === "code" && <Input disabled={disabled} onBlur={form.submit} />}
|
||||||
{type === "enum" && (
|
{type === "enum" && (
|
||||||
<Select disabled={disabled} onBlur={form.submit} onChange={form.submit}>
|
<Select disabled={disabled} onBlur={form.submit} onChange={form.submit}>
|
||||||
{v.options?.map((value, index) => {
|
{arg.options?.map((value, index) => {
|
||||||
return (
|
return (
|
||||||
<Select.Option key={index} value={value.value}>
|
<Select.Option key={index} value={value.value}>
|
||||||
{value.name}
|
{value.name}
|
||||||
@@ -467,6 +729,7 @@ const NodeInspector: FC = () => {
|
|||||||
)}
|
)}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -478,6 +741,68 @@ const NodeInspector: FC = () => {
|
|||||||
{def.output.map((v, i) => {
|
{def.output.map((v, i) => {
|
||||||
const required = v.indexOf("?") === -1;
|
const required = v.indexOf("?") === -1;
|
||||||
const desc = v.replace("?", "");
|
const desc = v.replace("?", "");
|
||||||
|
if (isVariadic(def.output!, i)) {
|
||||||
|
return (
|
||||||
|
<Form.Item label={desc} key={`output.${i}`}>
|
||||||
|
<Form.List name={`output.${i}`}>
|
||||||
|
{(fields, { add, remove }, { errors }) => (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
rowGap: 0,
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{fields.map((field) => (
|
||||||
|
<Flex key={field.key} gap={4}>
|
||||||
|
<Form.Item
|
||||||
|
name={field.name}
|
||||||
|
validateTrigger={["onChange", "onBlur"]}
|
||||||
|
style={{ width: "100%", marginBottom: 5 }}
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
disabled={disabled}
|
||||||
|
options={inoutVarOptions}
|
||||||
|
onBlur={form.submit}
|
||||||
|
onInputKeyDown={(e) =>
|
||||||
|
e.code === Hotkey.Escape && e.preventDefault()
|
||||||
|
}
|
||||||
|
filterOption={(value: string, option?: OptionType) => {
|
||||||
|
const label = option!.label as string;
|
||||||
|
return (
|
||||||
|
label.toUpperCase().indexOf(value.toUpperCase()) !== -1
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<MinusCircleOutlined
|
||||||
|
style={{ marginBottom: "6px" }}
|
||||||
|
onClick={() => {
|
||||||
|
remove(field.name);
|
||||||
|
form.submit();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
<Form.Item>
|
||||||
|
<Button
|
||||||
|
type="dashed"
|
||||||
|
onClick={() => {
|
||||||
|
add("");
|
||||||
|
}}
|
||||||
|
style={{ width: fields.length === 0 ? "100%" : "200px" }}
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
>
|
||||||
|
{t("node.output.add")}
|
||||||
|
</Button>
|
||||||
|
<Form.ErrorList errors={errors} />
|
||||||
|
</Form.Item>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Form.List>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={desc}
|
label={desc}
|
||||||
@@ -490,13 +815,14 @@ const NodeInspector: FC = () => {
|
|||||||
options={inoutVarOptions}
|
options={inoutVarOptions}
|
||||||
onBlur={form.submit}
|
onBlur={form.submit}
|
||||||
onInputKeyDown={(e) => e.code === Hotkey.Escape && e.preventDefault()}
|
onInputKeyDown={(e) => e.code === Hotkey.Escape && e.preventDefault()}
|
||||||
filterOption={(inputValue: string, option?: OptionType) => {
|
filterOption={(value: 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().indexOf(value.toUpperCase()) !== -1;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -670,7 +996,7 @@ export const Inspector: FC = () => {
|
|||||||
<Flex
|
<Flex
|
||||||
vertical
|
vertical
|
||||||
className="b3-inspector"
|
className="b3-inspector"
|
||||||
style={{ height: "100%", width: "340px", borderLeft: `1px solid var(--b3-color-border)` }}
|
style={{ height: "100%", width: "360px", borderLeft: `1px solid var(--b3-color-border)` }}
|
||||||
>
|
>
|
||||||
{isEditingNodeDef && <NodeDefInspector />}
|
{isEditingNodeDef && <NodeDefInspector />}
|
||||||
{isEditingTree && <TreeInspector />}
|
{isEditingTree && <TreeInspector />}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { useWorkspace } from "@/contexts/workspace-context";
|
import { useWorkspace } from "@/contexts/workspace-context";
|
||||||
|
import { NodeDef } from "@/misc/b3type";
|
||||||
import { Hotkey, isMacos } from "@/misc/keys";
|
import { Hotkey, isMacos } from "@/misc/keys";
|
||||||
import Path from "@/misc/path";
|
import Path from "@/misc/path";
|
||||||
import { SearchOutlined } from "@ant-design/icons";
|
import { SearchOutlined } from "@ant-design/icons";
|
||||||
import { Button, Flex, LayoutProps, Select } from "antd";
|
import { Button, Flex, LayoutProps, Select } from "antd";
|
||||||
import React, { FC, useMemo } from "react";
|
import React, { FC, useMemo } from "react";
|
||||||
import { Menu } from "./menu";
|
import { Menu } from "./menu";
|
||||||
import { NodeDef } from "@/misc/b3type";
|
|
||||||
|
|
||||||
interface OptionType {
|
interface OptionType {
|
||||||
label: string | React.ReactNode;
|
label: string | React.ReactNode;
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ import { NodeDef, NodeModel, TreeGraphData, TreeModel, unknownNodeDef } from "@/
|
|||||||
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";
|
||||||
import { readJson, writeJson } from "@/misc/util";
|
|
||||||
import Path from "@/misc/path";
|
import Path from "@/misc/path";
|
||||||
|
import { readJson, writeJson } from "@/misc/util";
|
||||||
import { Matrix, TreeGraph } from "@antv/g6";
|
import { Matrix, TreeGraph } from "@antv/g6";
|
||||||
import { BrowserWindow, dialog } from "@electron/remote";
|
import { BrowserWindow, dialog } from "@electron/remote";
|
||||||
|
import { ipcRenderer } from "electron";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { useSetting } from "./setting-context";
|
import { useSetting } from "./setting-context";
|
||||||
import { ipcRenderer } from "electron";
|
|
||||||
import { Editor } from "../components/editor";
|
|
||||||
|
|
||||||
let buildDir: string | undefined;
|
let buildDir: string | undefined;
|
||||||
|
|
||||||
|
|||||||
+118
-57
@@ -1,11 +1,11 @@
|
|||||||
import { useWorkspace } from "@/contexts/workspace-context";
|
import { useWorkspace } from "@/contexts/workspace-context";
|
||||||
import { NodeArg, NodeDef, NodeModel, TreeGraphData, TreeModel } from "@/misc/b3type";
|
import { NodeArg, NodeDef, NodeModel, TreeGraphData, TreeModel } from "@/misc/b3type";
|
||||||
|
import i18n from "@/misc/i18n";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { message } from "./hooks";
|
import { message } from "./hooks";
|
||||||
|
import { isMacos } from "./keys";
|
||||||
import Path from "./path";
|
import Path from "./path";
|
||||||
import { zhNodeDef } from "./template";
|
import { zhNodeDef } from "./template";
|
||||||
import i18n from "@/misc/i18n";
|
|
||||||
import { isMacos } from "./keys";
|
|
||||||
|
|
||||||
let ctx: CanvasRenderingContext2D | null = null;
|
let ctx: CanvasRenderingContext2D | null = null;
|
||||||
let defaultFontSize = "";
|
let defaultFontSize = "";
|
||||||
@@ -110,7 +110,86 @@ const error = (data: NodeModel | TreeGraphData, msg: string) => {
|
|||||||
console.error(`check ${data.id}|${data.name}: ${msg}`);
|
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,
|
data: NodeModel | TreeGraphData,
|
||||||
conf: NodeDef,
|
conf: NodeDef,
|
||||||
i: number,
|
i: number,
|
||||||
@@ -118,63 +197,23 @@ const checkNodeArg = (
|
|||||||
) => {
|
) => {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
const arg = conf.args![i] as NodeArg;
|
const arg = conf.args![i] as NodeArg;
|
||||||
const type = arg.type.replace("?", "") as NodeArg["type"];
|
|
||||||
const value = data.args?.[arg.name];
|
const value = data.args?.[arg.name];
|
||||||
if (type === "float") {
|
if (isNodeArgArray(arg)) {
|
||||||
const isNumber = typeof value === "number";
|
if (!Array.isArray(value)) {
|
||||||
const isOptional = value === undefined && arg.type === "float?";
|
|
||||||
if (!(isNumber || isOptional)) {
|
|
||||||
if (verbose) {
|
if (verbose) {
|
||||||
error(data, `'${arg.name}=${value}' is not a number`);
|
error(data, `'${arg.name}=${value}' is not an array`);
|
||||||
}
|
}
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
} else {
|
||||||
} else if (type === "int") {
|
for (let j = 0; j < value.length; j++) {
|
||||||
const isInt = typeof value === "number" && value === Math.floor(value);
|
if (!checkNodeArgValue(data, arg, value[j], verbose)) {
|
||||||
const isOptional = value === undefined && arg.type === "int?";
|
|
||||||
if (!(isInt || isOptional)) {
|
|
||||||
if (verbose) {
|
|
||||||
error(data, `'${arg.name}=${value}' is not a int`);
|
|
||||||
}
|
|
||||||
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`);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if (!checkNodeArgValue(data, arg, value, verbose)) {
|
||||||
hasError = true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (arg.oneof !== undefined) {
|
if (arg.oneof !== undefined) {
|
||||||
const idx = conf.input?.findIndex((v) => v.startsWith(arg.oneof!)) ?? -1;
|
const idx = conf.input?.findIndex((v) => v.startsWith(arg.oneof!)) ?? -1;
|
||||||
if (!checkOneof(data.args?.[arg.name], data.input?.[idx])) {
|
if (!checkOneof(data.args?.[arg.name], data.input?.[idx])) {
|
||||||
@@ -210,6 +249,8 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
|||||||
error(data, `expect ${conf.children} children, but got ${count}`);
|
error(data, `expect ${conf.children} children, but got ${count}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasVaridicInput = false;
|
||||||
if (conf.input) {
|
if (conf.input) {
|
||||||
for (let i = 0; i < conf.input.length; i++) {
|
for (let i = 0; i < conf.input.length; i++) {
|
||||||
if (!data.input) {
|
if (!data.input) {
|
||||||
@@ -218,15 +259,20 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
|||||||
if (!data.input[i]) {
|
if (!data.input[i]) {
|
||||||
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`);
|
error(data, `intput field '${conf.input[i]}' is required`);
|
||||||
hasError = true;
|
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;
|
data.input.length = conf.input?.length || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hasVaridicOutput = false;
|
||||||
if (conf.output) {
|
if (conf.output) {
|
||||||
for (let i = 0; i < conf.output.length; i++) {
|
for (let i = 0; i < conf.output.length; i++) {
|
||||||
if (!data.output) {
|
if (!data.output) {
|
||||||
@@ -235,13 +281,16 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
|||||||
if (!data.output[i]) {
|
if (!data.output[i]) {
|
||||||
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`);
|
error(data, `output field '${conf.output[i]}' is required`);
|
||||||
hasError = true;
|
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;
|
data.output.length = conf.output?.length || 0;
|
||||||
}
|
}
|
||||||
if (conf.args) {
|
if (conf.args) {
|
||||||
@@ -456,18 +505,30 @@ export const checkChildrenLimit = (data: TreeGraphData) => {
|
|||||||
return true;
|
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) => {
|
export const checkTreeData = (data: TreeGraphData) => {
|
||||||
const conf = data.def;
|
const conf = data.def;
|
||||||
if (conf.input) {
|
if (conf.input) {
|
||||||
for (let i = 0; i < conf.input.length; i++) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (conf.output) {
|
if (conf.output) {
|
||||||
for (let i = 0; i < conf.output.length; i++) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+50
-37
@@ -32,34 +32,6 @@ export const zhNodeDef = () => {
|
|||||||
],
|
],
|
||||||
doc: "+ 只能有一个子节点,多个仅执行第一个\n+ 当子节点返回 `failure` 时,抛出异常\n+ 其余情况返回子节点的执行状态\n",
|
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",
|
name: "Calculate",
|
||||||
type: "Action",
|
type: "Action",
|
||||||
@@ -138,6 +110,24 @@ export const zhNodeDef = () => {
|
|||||||
output: ["变量"],
|
output: ["变量"],
|
||||||
doc: "+ 只能有一个子节点,多个仅执行第一个\n+ 遍历输入数组,将当前元素写入`变量`\n+ 当子节点返回 `failure` 时,退出遍历并返回 `failure` 状态\n+ 执行完所有子节点后,返回 `success`",
|
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",
|
name: "IfElse",
|
||||||
type: "Composite",
|
type: "Composite",
|
||||||
@@ -160,18 +150,18 @@ export const zhNodeDef = () => {
|
|||||||
type: "Action",
|
type: "Action",
|
||||||
children: 0,
|
children: 0,
|
||||||
status: ["success", "failure"],
|
status: ["success", "failure"],
|
||||||
desc: "索引输入的数组或对象",
|
desc: "索引输入的数组",
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "idx",
|
name: "index",
|
||||||
type: "string?",
|
type: "int?",
|
||||||
desc: "索引",
|
desc: "索引",
|
||||||
oneof: "索引",
|
oneof: "索引",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
input: ["输入目标", "索引?"],
|
input: ["数组", "索引?"],
|
||||||
output: ["输出目标"],
|
output: ["值"],
|
||||||
doc: "+ 合法元素不包括 `undefined` 和 `null`\n+ 只有索引到有合法元素时候才会返回 `success`,否则返回 `failure`\n",
|
doc: "+ 合法元素不包括 `undefined` 和 `null`\n+ 索引数组的时候,第一个元素的索引为 0\n+ 只有索引到有合法元素时候才会返回 `success`,否则返回 `failure`\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Invert",
|
name: "Invert",
|
||||||
@@ -198,14 +188,14 @@ export const zhNodeDef = () => {
|
|||||||
input: ["已存在变量名?"],
|
input: ["已存在变量名?"],
|
||||||
args: [
|
args: [
|
||||||
{
|
{
|
||||||
name: "expr",
|
name: "value",
|
||||||
type: "json?",
|
type: "json?",
|
||||||
desc: "表达式",
|
desc: "值(value)",
|
||||||
oneof: "已存在变量名",
|
oneof: "已存在变量名",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
output: ["新变量名"],
|
output: ["新变量名"],
|
||||||
doc: "+ 如果有输入变量,则给已有变量重新定义一个名字\n+ 如果有表达式,则使用表达式\n+ 如果表达式为 `null`,则清除变量\n",
|
doc: "+ 如果有输入变量,则给已有变量重新定义一个名字\n+ 如果`值(value)`为 `null`,则清除变量\n",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Listen",
|
name: "Listen",
|
||||||
@@ -441,6 +431,29 @@ export const zhNodeDef = () => {
|
|||||||
desc: "顺序执行",
|
desc: "顺序执行",
|
||||||
doc: "+ 一直往下执行,只有当所有子节点都返回 `success`, 才返回 `success`\n+ 若子节点返回 `failure`,则直接返回 `failure` 状态\n+ 其余情况返回 `running` 状态\n",
|
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",
|
name: "Timeout",
|
||||||
type: "Decorator",
|
type: "Decorator",
|
||||||
|
|||||||
Reference in new issue
Block a user