import { EditNode, EditTree, useWorkspace } from "@/contexts/workspace-context";
import { NodeArgType, NodeModel, TreeGraphData, TreeModel } from "@/misc/b3type";
import { Hotkey, isMacos } from "@/misc/keys";
import Path from "@/misc/path";
import { AutoComplete, Divider, Flex, Form, Input, InputNumber, Select, Switch } from "antd";
import { DefaultOptionType } from "antd/es/select";
import { FC, useEffect, useMemo } from "react";
import { useTranslation } from "react-i18next";
import Markdown from "react-markdown";
interface OptionType extends DefaultOptionType {}
const TreeInspector: FC = () => {
const workspace = {
editing: useWorkspace((state) => state.editing),
editingTree: useWorkspace((state) => state.editingTree)!,
};
const { t } = useTranslation();
const [form] = Form.useForm();
// set form values
useEffect(() => {
const data = workspace.editingTree.data;
form.resetFields();
form.setFieldValue("name", data.name);
form.setFieldValue("desc", data.desc);
}, [workspace.editingTree]);
const finish = (values: any) => {
const data = {} as TreeModel;
data.name = values.name;
data.desc = values.desc || undefined;
workspace.editing?.dispatch("updateTree", {
data: {
name: values.name,
desc: values.desc || undefined,
},
} as EditTree);
};
return (
<>
{t("tree.overview")}
>
);
};
const NodeInspector: FC = () => {
const workspace = {
editing: useWorkspace((state) => state.editing),
editingNode: useWorkspace((state) => state.editingNode)!,
getNodeDef: useWorkspace((state) => state.getNodeDef),
nodeDefs: useWorkspace((state) => state.nodeDefs),
onEditingNode: useWorkspace((state) => state.onEditingNode),
allFiles: useWorkspace((state) => state.allFiles),
fileTree: useWorkspace((state) => state.fileTree),
};
const { t } = useTranslation();
const [form] = Form.useForm();
// set form values
useEffect(() => {
const data = workspace.editingNode.data;
const def = workspace.getNodeDef(workspace.editingNode.data.name);
form.resetFields();
form.setFieldValue("id", data.id);
form.setFieldValue("name", data.name);
form.setFieldValue("type", def.type);
form.setFieldValue("desc", data.desc);
form.setFieldValue("debug", data.debug);
form.setFieldValue("path", data.path);
def.args?.forEach((v) => {
form.setFieldValue(`args.${v.name}`, data.args?.[v.name]);
});
def.input?.forEach((_, i) => {
form.setFieldValue(`input.${i}`, data.input?.[i]);
});
def.output?.forEach((_, i) => {
form.setFieldValue(`output.${i}`, data.output?.[i]);
});
}, [workspace.editingNode]);
// auto complete for node
const nodeOptions = useMemo(() => {
const options: OptionType[] = [];
workspace.nodeDefs.forEach((e) => {
options.push({ label: `${e.name}(${e.desc})`, value: e.name });
});
return options;
}, [workspace.nodeDefs]);
// auto complete for input and output
const inoutVarOptions = useMemo(() => {
const options: OptionType[] = [];
const collect = (node?: TreeGraphData) => {
if (node) {
node.input?.forEach((v, i) => {
const desc = node.def.input?.[i] ?? "";
if (v && !options.find((option) => option.value === v)) {
options.push({ label: `${v}(${desc})`, value: v });
}
});
node.output?.forEach((v, i) => {
const desc = node.def.output?.[i] ?? "";
if (v && !options.find((option) => option.value === v)) {
options.push({ label: `${v}(${desc})`, value: v });
}
});
node.children?.forEach((child) => collect(child));
}
};
collect(workspace.editing?.data);
return options;
}, [workspace.editing]);
// auto complete for subtree
const subtreeOptions = useMemo(() => {
const options: OptionType[] = [];
workspace.allFiles.forEach((path) => {
const value = Path.relative(workspace.fileTree!.path, path).replaceAll(Path.sep, "/");
const desc = ""; //fileNode.desc ? `(${fileNode.desc})` : "";
options.push({
label: `${value}${desc}`,
value: value,
});
});
return options;
}, [workspace.allFiles, workspace.fileTree]);
const editingNode = workspace.editingNode;
const def = workspace.getNodeDef(editingNode.data.name);
const disabled = !editingNode.editable;
// update value
const finish = (values: any) => {
const data = {} as NodeModel;
data.id = editingNode.data.id;
data.name = values.name;
data.debug = values.debug || undefined;
data.desc = values.desc || undefined;
data.path = values.path || undefined;
def.args?.forEach((arg) => {
const v = values[`args.${arg.name}`];
if (v !== null && v !== undefined && v !== "") {
data.args ||= {};
data.args[arg.name] = v;
}
});
def.input?.forEach((_, i) => {
const v = values[`input.${i}`];
data.input ||= [];
data.input.push(v ?? "");
});
def.output?.forEach((_, i) => {
const v = values[`output.${i}`];
data.output ||= [];
data.output.push(v ?? "");
});
workspace.editing?.dispatch("updateNode", {
data: data,
} as EditNode);
};
// change node def
const changeNodeDef = (newname: string) => {
if (editingNode.data.name !== newname) {
workspace.onEditingNode({
data: {
id: editingNode.data.id,
name: workspace.nodeDefs.get(newname)?.name ?? newname,
desc: editingNode.data.desc,
debug: editingNode.data.debug,
},
editable: editingNode.editable,
});
finish(form.getFieldsValue());
} else {
form.submit();
}
};
const changeSubtree = () => {
if (form.getFieldValue("path") !== editingNode.data.path) {
finish(form.getFieldsValue());
} else {
form.submit();
}
};
return (
<>
{def.desc}
{t("node.id")}
}
>
{t("node.type")}
}
>
changeNodeDef(form.getFieldValue("name"))}
onSelect={changeNodeDef}
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;
}}
/>
e.code === Hotkey.Escape && e.preventDefault()}
filterOption={(inputValue: string, option?: OptionType) => {
const label = option!.label as string;
return label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
}}
/>
{def.doc}
{def.input && def.input.length > 0 && (
<>
{t("node.inputVariable")}
{def.input.map((v, i) => {
const required = v.indexOf("?") == -1;
const desc = v.replace("?", "");
return (
e.code === Hotkey.Escape && e.preventDefault()}
filterOption={(inputValue: string, option?: OptionType) => {
const label = option!.label as string;
return label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
}}
/>
);
})}
>
)}
{def.args && def.args.length > 0 && (
<>
{t("node.args")}
{def.args.map((v) => {
const required = v.type.indexOf("?") == -1;
const type = v.type.replace("?", "") as NodeArgType;
return (
{type === "string" && }
{type === "int" && (
)}
{type === "float" && }
{type === "boolean" && }
{type === "code" && (
)}
{type === "enum" && (
)}
);
})}
>
)}
{def.output && def.output.length > 0 && (
<>
{t("node.outputVariable")}
{def.output.map((v, i) => {
const required = v.indexOf("?") == -1;
const desc = v.replace("?", "");
return (
e.code === Hotkey.Escape && e.preventDefault()}
filterOption={(inputValue: string, option?: OptionType) => {
const label = option!.label as string;
return label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1;
}}
/>
);
})}
>
)}
>
);
};
const NodeDefInspector: FC = () => {
const workspace = {
editingNodeDef: useWorkspace((state) => state.editingNodeDef)!,
};
const { t } = useTranslation();
const [form] = Form.useForm();
const data = workspace.editingNodeDef.data;
// set form values
useEffect(() => {
form.resetFields();
form.setFieldValue("name", data.name);
form.setFieldValue("type", data.type);
form.setFieldValue("desc", data.desc);
form.setFieldValue("doc", data.doc);
data.input?.forEach((v, i) => {
form.setFieldValue(`input.${i}.name`, v.replaceAll("?", ""));
});
data.output?.forEach((v, i) => {
form.setFieldValue(`output.${i}.name`, v.replaceAll("?", ""));
});
data.args?.forEach((v, i) => {
form.setFieldValue(`args.${i}.type`, v.type.replaceAll("?", ""));
});
}, [workspace.editingNodeDef]);
return (
<>
{t("nodeDefinition")}
{t("node.name")}
}
>
{t("node.type")}
}
>
{t("node.desc")}
}
>
{data.doc}
{data.input && data.input.length > 0 && (
<>
{t("node.inputVariable")}
{data.input.map((v, i) => {
const required = v.indexOf("?") == -1;
return (
);
})}
>
)}
{data.args && data.args.length > 0 && (
<>
{t("node.args")}
{data.args.map((v, i) => {
const required = v.type.indexOf("?") == -1;
const type = v.type.replace("?", "") as NodeArgType;
return (
);
})}
>
)}
{data.output && data.output.length > 0 && (
<>
{t("node.outputVariable")}
{data.output.map((v, i) => {
const required = v.indexOf("?") == -1;
return (
);
})}
>
)}
>
);
};
export const Inspector: FC = () => {
const workspace = {
editingNode: useWorkspace((state) => state.editingNode),
editingTree: useWorkspace((state) => state.editingTree),
editingNodeDef: useWorkspace((state) => state.editingNodeDef),
};
let isEditingNode = false;
let isEditingTree = false;
let isEditingNodeDef = false;
if (workspace.editingNodeDef) {
isEditingNodeDef = true;
} else if (workspace.editingTree) {
isEditingTree = true;
} else if (workspace.editingNode) {
isEditingNode = true;
}
return (
{isEditingNodeDef && }
{isEditingTree && }
{isEditingNode && }
);
};