import { AimOutlined, EditOutlined, FormOutlined, MinusCircleOutlined, PlusOutlined, } from "@ant-design/icons"; import { AutoComplete, Button, Divider, Flex, Form, Input, InputNumber, Select, Space, Switch, } from "antd"; import useFormInstance from "antd/es/form/hooks/useFormInstance"; import TextArea from "antd/es/input/TextArea"; import { DefaultOptionType } from "antd/es/select"; import { FC, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import Markdown from "react-markdown"; import { useDebounceCallback } from "usehooks-ts"; import { useShallow } from "zustand/react/shallow"; import { ExpressionEvaluator } from "../behavior3/src/behavior3"; import { EditNode, EditTree, useWorkspace } from "../contexts/workspace-context"; import { hasArgOptions, ImportDef, isBoolType, isExprType, isFloatType, isIntType, isJsonType, isStringType, NodeArg, NodeModel, TreeGraphData, VarDef, } from "../misc/b3type"; import { checkNodeArgValue, checkOneof, getNodeArgRawType, isNodeArgArray, isNodeArgOptional, isValidVariableName, isVariadic, parseExpr, usingGroups, usingVars, } from "../misc/b3util"; import i18n from "../misc/i18n"; import { Hotkey, isMacos } from "../misc/keys"; import { mergeClassNames } from "../misc/util"; interface OptionType extends DefaultOptionType { value: string; } interface VarItem extends VarDef { count?: number; } interface VarItemProps { name: number; disabled?: boolean; value?: VarItem; onChange?: (vardef: VarItem) => void; onRemove?: (name: number | number[]) => void; } const VarDefItem: FC = ({ name, onChange, onRemove, disabled, ...props }) => { const { t } = useTranslation(); const form = useFormInstance(); const [value, setValue] = useState(props.value ?? { name: "", desc: "" }); const { editing } = useWorkspace( useShallow((state) => ({ editing: state.editing, })) ); const onSubmit = () => { onChange?.(value); form.submit(); }; return (
value.name && editing?.dispatch("clickVar", value.name)} > {value?.count ?? 0}
setValue({ ...value, name: e.target.value })} /> setValue({ ...value, desc: e.target.value })} />
{!disabled && ( { onRemove?.(name); form.submit(); }} /> )} {disabled &&
} ); }; const TreeInspector: FC = () => { const workspace = useWorkspace( useShallow((state) => ({ allFiles: state.allFiles, editing: state.editing, editingTree: state.editingTree!, fileTree: state.fileTree, groupDefs: state.groupDefs, nodeDefs: state.nodeDefs, relative: state.relative, open: state.open, workdir: state.workdir, })) ); const { t } = useTranslation(); const [form] = Form.useForm(); // using count const usingCount: Record = useMemo(() => { const count: Record = {}; const collect = (node: TreeGraphData) => { const def = workspace.nodeDefs.get(node.name); if (def.input) { node.input?.forEach((v) => { count[v] = (count[v] ?? 0) + 1; }); } if (def.output) { node.output?.forEach((v) => { count[v] = (count[v] ?? 0) + 1; }); } if (def.args) { def.args.forEach((arg) => { const expr = node.args?.[arg.name] as string | string[] | undefined; if (!isExprType(arg.type) || !expr) { return; } if (Array.isArray(expr)) { expr.forEach((str) => { parseExpr(str).forEach((v) => { count[v] = (count[v] ?? 0) + 1; }); }); } else { parseExpr(expr).forEach((v) => { count[v] = (count[v] ?? 0) + 1; }); } }); } node.children?.forEach(collect); }; collect(workspace.editingTree.root); return count; }, [workspace.editingTree, workspace.nodeDefs]); // 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]); // set form values useEffect(() => { form.resetFields(); form.setFieldValue("name", workspace.editingTree.name); form.setFieldValue("desc", workspace.editingTree.desc); form.setFieldValue("export", workspace.editingTree.export !== false); form.setFieldValue("firstid", workspace.editingTree.firstid); form.setFieldValue("group", workspace.editingTree.group); form.setFieldValue( "declvar", workspace.editingTree.declvar.map((v) => ({ name: v.name, desc: v.desc, count: usingCount[v.name] ?? 0, })) ); form.setFieldValue( "import", workspace.editingTree.import.map((entry) => ({ path: entry.path, vars: entry.vars.map((v) => ({ name: v.name, desc: v.desc, count: usingCount[v.name] ?? 0, })), })) ); form.setFieldValue( "subtree", workspace.editingTree.subtree.map((entry) => ({ path: entry.path, vars: entry.vars.map((v) => ({ name: v.name, desc: v.desc, count: usingCount[v.name] ?? 0, })), })) ); }, [workspace.editingTree, usingCount]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const finish = (values: any) => { workspace.editing?.dispatch("updateTree", { name: values.name, desc: values.desc, export: values.export, firstid: Number(values.firstid), group: ((values.group ?? []) as string[]).filter((g) => g).sort((a, b) => a.localeCompare(b)), declvar: (values.declvar as VarDef[]) .filter((v) => v && v.name) .map((v) => ({ name: v.name, desc: v.desc, })) .sort((a, b) => a.name.localeCompare(b.name)), import: (values.import as ImportDef[]) .filter((v) => v && v.path) .sort((a, b) => a.path.localeCompare(b.path)) .map((v) => ({ path: v.path, vars: (v.vars ?? []).map((v1) => ({ name: v1.name, desc: v1.desc })), })), } as EditTree); }; return ( <>
{t("tree.overview")}
<>