Merge branch 'dev'
This commit is contained in:
commit
78925c4ae5
14 files changed
+258
-108
No files matched your search
@@ -6,9 +6,6 @@
|
|||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
"[json]": {
|
|
||||||
"editor.formatOnSave": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "1.1.2",
|
"version": "1.1.3",
|
||||||
"main": "dist-electron/main/index.js",
|
"main": "dist-electron/main/index.js",
|
||||||
"description": "行为树编辑器",
|
"description": "行为树编辑器",
|
||||||
"author": "zhandouxiaojiji & codetypes",
|
"author": "zhandouxiaojiji & codetypes",
|
||||||
|
|||||||
@@ -124,7 +124,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MoveToPos",
|
"name": "MoveToPos",
|
||||||
"type": "Action",
|
"type": "Action (Client)",
|
||||||
"args": [
|
"args": [
|
||||||
{
|
{
|
||||||
"name": "x",
|
"name": "x",
|
||||||
@@ -141,7 +141,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "MoveToTarget",
|
"name": "MoveToTarget",
|
||||||
"type": "Action",
|
"type": "Action (Server)",
|
||||||
"input": [
|
"input": [
|
||||||
"{目标}"
|
"{目标}"
|
||||||
],
|
],
|
||||||
|
|||||||
+106
-5
@@ -10,17 +10,19 @@ import * as b3util from "@/misc/b3util";
|
|||||||
import { message } from "@/misc/hooks";
|
import { message } from "@/misc/hooks";
|
||||||
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
||||||
import Path from "@/misc/path";
|
import Path from "@/misc/path";
|
||||||
|
import { ArrowDownOutlined, ArrowUpOutlined, CloseOutlined } from "@ant-design/icons";
|
||||||
import G6, { G6GraphEvent, Item, TreeGraph } from "@antv/g6";
|
import G6, { G6GraphEvent, Item, TreeGraph } from "@antv/g6";
|
||||||
import { dialog } from "@electron/remote";
|
import { dialog } from "@electron/remote";
|
||||||
import { useSize } from "ahooks";
|
import { useSize } from "ahooks";
|
||||||
import { Dropdown, Flex, FlexProps, MenuProps } from "antd";
|
import { Button, Dropdown, Flex, FlexProps, Input, MenuProps } from "antd";
|
||||||
import { clipboard } from "electron";
|
import { clipboard } from "electron";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import React, { FC, useCallback, useEffect, useMemo, useRef } from "react";
|
import React, { FC, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FiDelete } from "react-icons/fi";
|
import { FiDelete } from "react-icons/fi";
|
||||||
import { IoMdReturnLeft } from "react-icons/io";
|
import { IoMdReturnLeft } from "react-icons/io";
|
||||||
import { mergeRefs } from "react-merge-refs";
|
import { mergeRefs } from "react-merge-refs";
|
||||||
|
import { useDebounceCallback } from "usehooks-ts";
|
||||||
import "./register-node";
|
import "./register-node";
|
||||||
|
|
||||||
export interface EditorProps extends React.HTMLAttributes<HTMLElement> {
|
export interface EditorProps extends React.HTMLAttributes<HTMLElement> {
|
||||||
@@ -36,11 +38,27 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
open: useWorkspace((state) => state.open),
|
open: useWorkspace((state) => state.open),
|
||||||
workdir: useWorkspace((state) => state.workdir),
|
workdir: useWorkspace((state) => state.workdir),
|
||||||
};
|
};
|
||||||
|
const [results, setResults] = useState<string[]>([]);
|
||||||
|
const [resultIndex, setResultIndex] = useState(0);
|
||||||
|
const [showingSearch, setShowingSearch] = useState(false);
|
||||||
const graphRef = useRef(null);
|
const graphRef = useRef(null);
|
||||||
const sizeRef = useRef(null);
|
const sizeRef = useRef(null);
|
||||||
const editorSize = useSize(sizeRef);
|
const editorSize = useSize(sizeRef);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onSearchChange = useDebounceCallback((filter: string) => {
|
||||||
|
const ids = filterNodes(filter, findDataById("1"));
|
||||||
|
setResults(ids);
|
||||||
|
setResultIndex(0);
|
||||||
|
if (ids.length > 0) {
|
||||||
|
editor.graph.focusItem(ids[0]);
|
||||||
|
selectNode(ids[0]);
|
||||||
|
}
|
||||||
|
if (filter.length == 0) {
|
||||||
|
restoreViewport();
|
||||||
|
}
|
||||||
|
}, 200);
|
||||||
|
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
if (!editor.unsave) {
|
if (!editor.unsave) {
|
||||||
editor.unsave = true;
|
editor.unsave = true;
|
||||||
@@ -93,6 +111,17 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
return editor.graph.findDataById(id) as TreeGraphData;
|
return editor.graph.findDataById(id) as TreeGraphData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const filterNodes = (str: string, node: TreeGraphData | null, ids?: string[]): string[] => {
|
||||||
|
ids ||= [];
|
||||||
|
if (node && str) {
|
||||||
|
if (node.name.toLocaleLowerCase().indexOf(str) >= 0) {
|
||||||
|
ids.push(node.id);
|
||||||
|
}
|
||||||
|
node.children?.forEach((child: TreeGraphData) => filterNodes(str, child, ids));
|
||||||
|
}
|
||||||
|
return ids;
|
||||||
|
};
|
||||||
|
|
||||||
const isAncestor = (ancestor: TreeGraphData, node: TreeGraphData): boolean => {
|
const isAncestor = (ancestor: TreeGraphData, node: TreeGraphData): boolean => {
|
||||||
if (ancestor.id === node.parent) {
|
if (ancestor.id === node.parent) {
|
||||||
return true;
|
return true;
|
||||||
@@ -782,7 +811,6 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!editor.dispatch) {
|
|
||||||
editor.dispatch = (event: EditEvent, data: unknown) => {
|
editor.dispatch = (event: EditEvent, data: unknown) => {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
case "copy": {
|
case "copy": {
|
||||||
@@ -833,6 +861,10 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
updateNode(data as EditNode);
|
updateNode(data as EditNode);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "searchNode": {
|
||||||
|
setShowingSearch(true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case "editSubtree": {
|
case "editSubtree": {
|
||||||
editSubtree();
|
editSubtree();
|
||||||
break;
|
break;
|
||||||
@@ -843,7 +875,6 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!editorSize || (editorSize.width === 0 && editorSize.height === 0)) {
|
if (!editorSize || (editorSize.width === 0 && editorSize.height === 0)) {
|
||||||
@@ -956,7 +987,7 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
|
|
||||||
type MenuInfo = Parameters<Exclude<MenuProps["onClick"], undefined>>[0];
|
type MenuInfo = Parameters<Exclude<MenuProps["onClick"], undefined>>[0];
|
||||||
const onClick = useCallback((info: MenuInfo) => {
|
const onClick = useCallback((info: MenuInfo) => {
|
||||||
editor.dispatch?.(info.key as EditEvent);
|
editor.dispatch(info.key as EditEvent);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -967,6 +998,76 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
style={{ maxWidth: "inherit", maxHeight: "inherit" }}
|
style={{ maxWidth: "inherit", maxHeight: "inherit" }}
|
||||||
>
|
>
|
||||||
|
{showingSearch && (
|
||||||
|
<Flex
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
marginTop: "10px",
|
||||||
|
marginLeft: "10px",
|
||||||
|
backgroundColor: "#161b22",
|
||||||
|
padding: "4px 10px 4px 10px",
|
||||||
|
borderRadius: "4px",
|
||||||
|
borderLeft: "4px solid #f78166",
|
||||||
|
boxShadow: "0 0 8px 2px #0000005c",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
autoFocus
|
||||||
|
allowClear
|
||||||
|
size="small"
|
||||||
|
style={{ borderRadius: "2px" }}
|
||||||
|
onChange={(e) => onSearchChange(e.currentTarget.value)}
|
||||||
|
/>
|
||||||
|
<div style={{ padding: "0 10px 0 5px" }}>
|
||||||
|
{results.length ? `${resultIndex + 1}/${results.length}` : ""}
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowDownOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
disabled={results.length == 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const idx = (resultIndex + 1) % results.length;
|
||||||
|
setResultIndex(idx);
|
||||||
|
editor.graph.focusItem(results[idx]);
|
||||||
|
selectNode(results[idx]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<ArrowUpOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
disabled={results.length == 0}
|
||||||
|
onClick={() => {
|
||||||
|
if (results.length > 0) {
|
||||||
|
const idx = (resultIndex + results.length - 1) % results.length;
|
||||||
|
setResultIndex(idx);
|
||||||
|
console.log(idx, results[idx]);
|
||||||
|
editor.graph.focusItem(results[idx]);
|
||||||
|
selectNode(results[idx]);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon={<CloseOutlined />}
|
||||||
|
type="text"
|
||||||
|
size="small"
|
||||||
|
style={{ width: "30px" }}
|
||||||
|
onClick={() => {
|
||||||
|
setShowingSearch(false);
|
||||||
|
setResults([]);
|
||||||
|
setResultIndex(0);
|
||||||
|
keysRef.current?.focus();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Flex>
|
||||||
|
)}
|
||||||
|
|
||||||
<Dropdown menu={{ items: menuItems, onClick }} trigger={["contextMenu"]}>
|
<Dropdown menu={{ items: menuItems, onClick }} trigger={["contextMenu"]}>
|
||||||
<div tabIndex={-1} style={{ width: "100%", height: "100%" }} ref={graphRef} />
|
<div tabIndex={-1} style={{ width: "100%", height: "100%" }} ref={graphRef} />
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
+55
-26
@@ -1,4 +1,5 @@
|
|||||||
import { FileTreeType, useWorkspace } from "@/contexts/workspace-context";
|
import { FileTreeType, useWorkspace } from "@/contexts/workspace-context";
|
||||||
|
import { NodeDef, getNodeType } from "@/misc/b3type";
|
||||||
import * as b3util from "@/misc/b3util";
|
import * as b3util from "@/misc/b3util";
|
||||||
import { modal } from "@/misc/hooks";
|
import { modal } from "@/misc/hooks";
|
||||||
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
import { Hotkey, isHotkeyPressed, isMacos, useHotkeys } from "@/misc/keys";
|
||||||
@@ -31,6 +32,15 @@ type MenuEvent =
|
|||||||
| "move"
|
| "move"
|
||||||
| "duplicate";
|
| "duplicate";
|
||||||
|
|
||||||
|
export type NodeTreeType = {
|
||||||
|
title: string;
|
||||||
|
def?: NodeDef;
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
isLeaf?: boolean;
|
||||||
|
children?: NodeTreeType[];
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
const findFile = (path: string | undefined, node: FileTreeType): FileTreeType | undefined => {
|
const findFile = (path: string | undefined, node: FileTreeType): FileTreeType | undefined => {
|
||||||
if (!path) {
|
if (!path) {
|
||||||
return;
|
return;
|
||||||
@@ -89,11 +99,11 @@ const renameFile = (oldPath: string, newPath: string) => {
|
|||||||
for (const editor of workspace.editors) {
|
for (const editor of workspace.editors) {
|
||||||
if (isDirectory) {
|
if (isDirectory) {
|
||||||
if (editor.path.startsWith(oldPath)) {
|
if (editor.path.startsWith(oldPath)) {
|
||||||
editor.dispatch?.("rename", editor.path.replace(oldPath, newPath));
|
editor.dispatch("rename", editor.path.replace(oldPath, newPath));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (editor.path === oldPath) {
|
if (editor.path === oldPath) {
|
||||||
editor.dispatch?.("rename", newPath);
|
editor.dispatch("rename", newPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,7 +122,6 @@ export const Explorer: FC = () => {
|
|||||||
editors: useWorkspace((state) => state.editors),
|
editors: useWorkspace((state) => state.editors),
|
||||||
fileTree: useWorkspace((state) => state.fileTree),
|
fileTree: useWorkspace((state) => state.fileTree),
|
||||||
nodeDefs: useWorkspace((state) => state.nodeDefs),
|
nodeDefs: useWorkspace((state) => state.nodeDefs),
|
||||||
nodeTree: useWorkspace((state) => state.nodeTree),
|
|
||||||
workdir: useWorkspace((state) => state.workdir),
|
workdir: useWorkspace((state) => state.workdir),
|
||||||
onEditingNodeDef: useWorkspace((state) => state.onEditingNodeDef),
|
onEditingNodeDef: useWorkspace((state) => state.onEditingNodeDef),
|
||||||
open: useWorkspace((state) => state.open),
|
open: useWorkspace((state) => state.open),
|
||||||
@@ -134,26 +143,44 @@ export const Explorer: FC = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workspace.nodeTree) {
|
const nodeTree = useMemo(() => {
|
||||||
workspace.nodeTree.title = t("nodeDefinition");
|
const data: NodeTreeType = {
|
||||||
workspace.nodeTree.icon = (
|
title: t("nodeDefinition"),
|
||||||
|
icon: (
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
<PiTreeStructureFill size={19} />
|
<PiTreeStructureFill size={19} />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
),
|
||||||
workspace.nodeTree.children?.forEach((catalog) => {
|
children: [],
|
||||||
catalog.icon = (
|
style: {
|
||||||
|
fontWeight: "bold",
|
||||||
|
fontSize: "13px",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
workspace.nodeDefs.forEach((nodeDef) => {
|
||||||
|
let catalog = data.children?.find((nt) => nt.title === nodeDef.type);
|
||||||
|
if (!catalog) {
|
||||||
|
const type = getNodeType(nodeDef);
|
||||||
|
catalog = {
|
||||||
|
title: nodeDef.type,
|
||||||
|
children: [],
|
||||||
|
icon: (
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
<img
|
<img
|
||||||
className="b3-node-icon"
|
className="b3-node-icon"
|
||||||
style={{ width: "13px", height: "13px", color: "white" }}
|
style={{ width: "13px", height: "13px", color: "white" }}
|
||||||
src={`./icons/${catalog.title}.svg`}
|
src={`./icons/${type}.svg`}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
),
|
||||||
catalog.children?.forEach((node) => {
|
};
|
||||||
if (node.def?.icon) {
|
data.children?.push(catalog);
|
||||||
node.icon = (
|
}
|
||||||
|
catalog.children?.push({
|
||||||
|
title: `${nodeDef.name}(${nodeDef.desc})`,
|
||||||
|
isLeaf: true,
|
||||||
|
def: nodeDef,
|
||||||
|
icon: nodeDef.icon ? (
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
<img
|
<img
|
||||||
className="b3-node-icon"
|
className="b3-node-icon"
|
||||||
@@ -161,22 +188,24 @@ export const Explorer: FC = () => {
|
|||||||
style={{ width: "13px", height: "13px", color: "white" }}
|
style={{ width: "13px", height: "13px", color: "white" }}
|
||||||
src={
|
src={
|
||||||
process.env.VITE_DEV_SERVER_URL && isMacos
|
process.env.VITE_DEV_SERVER_URL && isMacos
|
||||||
? `${Path.basename(workspace.workdir)}/${node.def?.icon}`
|
? `${Path.basename(workspace.workdir)}/${nodeDef.icon}`
|
||||||
: `${workspace.workdir}/${node.def?.icon}`
|
: `${workspace.workdir}/${nodeDef.icon}`
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
) : (
|
||||||
} else {
|
|
||||||
node.icon = (
|
|
||||||
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
<Flex justify="center" align="center" style={{ height: "100%" }}>
|
||||||
<BsBoxFill style={{ width: "12px", height: "12px", color: "white" }} />{" "}
|
<BsBoxFill style={{ width: "12px", height: "12px", color: "white" }} />{" "}
|
||||||
</Flex>
|
</Flex>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
data.children?.sort((a, b) => a.title.localeCompare(b.title));
|
||||||
|
data.children?.forEach((child) =>
|
||||||
|
child.children?.sort((a, b) => a.title.localeCompare(b.title))
|
||||||
);
|
);
|
||||||
}
|
return data;
|
||||||
});
|
}, [t, workspace.nodeDefs]);
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (workspace.editing) {
|
if (workspace.editing) {
|
||||||
@@ -467,12 +496,12 @@ export const Explorer: FC = () => {
|
|||||||
fs.renameSync(node.path, newPath);
|
fs.renameSync(node.path, newPath);
|
||||||
for (const editor of workspace.editors) {
|
for (const editor of workspace.editors) {
|
||||||
if (editor.path.startsWith(node.path)) {
|
if (editor.path.startsWith(node.path)) {
|
||||||
editor.dispatch?.("rename", dest!.path + "/" + Path.basename(editor.path));
|
editor.dispatch("rename", dest!.path + "/" + Path.basename(editor.path));
|
||||||
}
|
}
|
||||||
console.log("editor move", editor.path === newPath, editor.path, newPath);
|
console.log("editor move", editor.path === newPath, editor.path, newPath);
|
||||||
if (editor.path.startsWith(newPath)) {
|
if (editor.path.startsWith(newPath)) {
|
||||||
console.log("editor reload", editor.path === newPath, editor.path, newPath);
|
console.log("editor reload", editor.path === newPath, editor.path, newPath);
|
||||||
editor.dispatch?.("reload");
|
editor.dispatch("reload");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -795,7 +824,7 @@ export const Explorer: FC = () => {
|
|||||||
<DirectoryTree
|
<DirectoryTree
|
||||||
tabIndex={-1}
|
tabIndex={-1}
|
||||||
fieldNames={{ key: "title" }}
|
fieldNames={{ key: "title" }}
|
||||||
treeData={workspace.nodeTree ? [workspace.nodeTree] : []}
|
treeData={nodeTree ? [nodeTree] : []}
|
||||||
draggable={{ icon: false, nodeDraggable: (node) => !!node.isLeaf }}
|
draggable={{ icon: false, nodeDraggable: (node) => !!node.isLeaf }}
|
||||||
onSelect={(_, info) => {
|
onSelect={(_, info) => {
|
||||||
const node = info.node;
|
const node = info.node;
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ const TreeInspector: FC = () => {
|
|||||||
const data = {} as TreeModel;
|
const data = {} as TreeModel;
|
||||||
data.name = values.name;
|
data.name = values.name;
|
||||||
data.desc = values.desc || undefined;
|
data.desc = values.desc || undefined;
|
||||||
workspace.editing?.dispatch?.("updateTree", {
|
workspace.editing?.dispatch("updateTree", {
|
||||||
data: {
|
data: {
|
||||||
name: values.name,
|
name: values.name,
|
||||||
desc: values.desc || undefined,
|
desc: values.desc || undefined,
|
||||||
@@ -80,6 +80,7 @@ const NodeInspector: FC = () => {
|
|||||||
form.resetFields();
|
form.resetFields();
|
||||||
form.setFieldValue("id", data.id);
|
form.setFieldValue("id", data.id);
|
||||||
form.setFieldValue("name", data.name);
|
form.setFieldValue("name", data.name);
|
||||||
|
form.setFieldValue("type", def.type);
|
||||||
form.setFieldValue("desc", data.desc);
|
form.setFieldValue("desc", data.desc);
|
||||||
form.setFieldValue("debug", data.debug);
|
form.setFieldValue("debug", data.debug);
|
||||||
form.setFieldValue("path", data.path);
|
form.setFieldValue("path", data.path);
|
||||||
@@ -173,7 +174,7 @@ const NodeInspector: FC = () => {
|
|||||||
data.output ||= [];
|
data.output ||= [];
|
||||||
data.output.push(v ?? "");
|
data.output.push(v ?? "");
|
||||||
});
|
});
|
||||||
workspace.editing?.dispatch?.("updateNode", {
|
workspace.editing?.dispatch("updateNode", {
|
||||||
data: data,
|
data: data,
|
||||||
} as EditNode);
|
} as EditNode);
|
||||||
};
|
};
|
||||||
@@ -227,6 +228,14 @@ const NodeInspector: FC = () => {
|
|||||||
>
|
>
|
||||||
<Input disabled={true} />
|
<Input disabled={true} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="type"
|
||||||
|
label={
|
||||||
|
<div style={{ minWidth: "80px", justifyContent: "flex-end" }}>{t("node.type")}</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Input disabled={true} />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label={t("node.name")} name="name">
|
<Form.Item label={t("node.name")} name="name">
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ export const Menu: FC<LayoutProps> = () => {
|
|||||||
label: t("insertNode"),
|
label: t("insertNode"),
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
click: () => {
|
click: () => {
|
||||||
workspace.editing?.dispatch?.("insert");
|
workspace.editing?.dispatch("insert");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -229,7 +229,7 @@ export const Menu: FC<LayoutProps> = () => {
|
|||||||
label: t("deleteNode"),
|
label: t("deleteNode"),
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
click: () => {
|
click: () => {
|
||||||
workspace.editing?.dispatch?.("delete");
|
workspace.editing?.dispatch("delete");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { useWorkspace } from "@/contexts/workspace-context";
|
import { useWorkspace } from "@/contexts/workspace-context";
|
||||||
import { TreeGraphData } from "@/misc/b3type";
|
import { TreeGraphData, getNodeType } from "@/misc/b3type";
|
||||||
import { checkTreeData, toBreakWord } from "@/misc/b3util";
|
import { checkTreeData, cutWordTo, toBreakWord } from "@/misc/b3util";
|
||||||
import i18n from "@/misc/i18n";
|
import i18n from "@/misc/i18n";
|
||||||
|
import { isMacos } from "@/misc/keys";
|
||||||
|
import Path from "@/misc/path";
|
||||||
import G6 from "@antv/g6";
|
import G6 from "@antv/g6";
|
||||||
|
|
||||||
const NODE_COLORS: any = {
|
const NODE_COLORS: any = {
|
||||||
@@ -73,7 +75,7 @@ G6.registerNode(
|
|||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
const workspace = useWorkspace.getState();
|
const workspace = useWorkspace.getState();
|
||||||
const nodeDef = workspace.getNodeDef(cfg.name as string);
|
const nodeDef = workspace.getNodeDef(cfg.name as string);
|
||||||
let classify = nodeDef.type || "Other";
|
let classify = getNodeType(nodeDef);
|
||||||
let color = nodeDef.color || NODE_COLORS[classify] || NODE_COLORS["Other"];
|
let color = nodeDef.color || NODE_COLORS[classify] || NODE_COLORS["Other"];
|
||||||
if (
|
if (
|
||||||
!workspace.hasNodeDef(cfg.name as string) ||
|
!workspace.hasNodeDef(cfg.name as string) ||
|
||||||
@@ -176,7 +178,11 @@ G6.registerNode(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// icon
|
// icon
|
||||||
const img = nodeDef.icon ? workspace.workdir + "/" + nodeDef.icon : `./icons/${classify}.svg`;
|
const img = nodeDef.icon
|
||||||
|
? process.env.VITE_DEV_SERVER_URL && isMacos
|
||||||
|
? `${Path.basename(workspace.workdir)}/${nodeDef.icon}`
|
||||||
|
: `${workspace.workdir}/${nodeDef.icon}`
|
||||||
|
: `./icons/${classify}.svg`;
|
||||||
addShape("image", {
|
addShape("image", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: 5,
|
x: 5,
|
||||||
@@ -216,11 +222,13 @@ G6.registerNode(
|
|||||||
name: "name-text",
|
name: "name-text",
|
||||||
});
|
});
|
||||||
|
|
||||||
const x = 5;
|
const x = 6;
|
||||||
let y = 24;
|
let y = 24;
|
||||||
// desc text
|
// desc text
|
||||||
const desc = cfg.desc || nodeDef.desc;
|
let desc = (cfg.desc || nodeDef.desc) as string;
|
||||||
if (desc) {
|
if (desc) {
|
||||||
|
desc = i18n.t("regnode.mark") + desc;
|
||||||
|
desc = cutWordTo(desc, 31);
|
||||||
addShape("text", {
|
addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
@@ -228,7 +236,7 @@ G6.registerNode(
|
|||||||
y,
|
y,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
text: `${i18n.t("regnode.mark")}${desc}`,
|
text: `${desc}`,
|
||||||
fill: "black",
|
fill: "black",
|
||||||
},
|
},
|
||||||
name: "desc-text",
|
name: "desc-text",
|
||||||
@@ -327,13 +335,15 @@ G6.registerNode(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.path) {
|
if (cfg.path) {
|
||||||
|
let path = (i18n.t("regnode.subtree") + cfg.path) as string;
|
||||||
|
path = cutWordTo(path, 33);
|
||||||
addShape("text", {
|
addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
x,
|
x,
|
||||||
y: y + 20,
|
y: y + 20,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
text: `${i18n.t("regnode.subtree")}${cfg.path}`,
|
text: `${path}`,
|
||||||
fill: "black",
|
fill: "black",
|
||||||
},
|
},
|
||||||
name: "subtree-text",
|
name: "subtree-text",
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ export const Workspace: FC = () => {
|
|||||||
const { width = 0, height = 0 } = useWindowSize();
|
const { width = 0, height = 0 } = useWindowSize();
|
||||||
|
|
||||||
const keysRef = useHotkeys<HTMLDivElement>(
|
const keysRef = useHotkeys<HTMLDivElement>(
|
||||||
[Hotkey.Save, Hotkey.CloseEditor, Hotkey.Search, Hotkey.Build],
|
[Hotkey.Save, Hotkey.CloseEditor, Hotkey.SearchTree, Hotkey.Build, Hotkey.SearchNode],
|
||||||
(event) => {
|
(event) => {
|
||||||
if (isHotkeyPressed(Hotkey.Save)) {
|
if (isHotkeyPressed(Hotkey.Save)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -58,12 +58,15 @@ export const Workspace: FC = () => {
|
|||||||
workspace.close(workspace.editing.path);
|
workspace.close(workspace.editing.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isHotkeyPressed(Hotkey.Search)) {
|
} else if (isHotkeyPressed(Hotkey.SearchTree)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
workspace.onShowingSearch(true);
|
workspace.onShowingSearch(true);
|
||||||
} else if (isHotkeyPressed(Hotkey.Build)) {
|
} else if (isHotkeyPressed(Hotkey.Build)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
workspace.buildProject();
|
workspace.buildProject();
|
||||||
|
} else if (isHotkeyPressed(Hotkey.SearchNode)) {
|
||||||
|
event.preventDefault();
|
||||||
|
workspace.editing?.dispatch("searchNode");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -95,7 +98,7 @@ export const Workspace: FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
editor.dispatch?.("save");
|
editor.dispatch("save");
|
||||||
workspace.close(editor.path);
|
workspace.close(editor.path);
|
||||||
alert.destroy();
|
alert.destroy();
|
||||||
keysRef.current?.focus();
|
keysRef.current?.focus();
|
||||||
@@ -163,7 +166,7 @@ export const Workspace: FC = () => {
|
|||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
unsaves.forEach((editor) => editor.dispatch?.("save"));
|
unsaves.forEach((editor) => editor.dispatch("save"));
|
||||||
alert.destroy();
|
alert.destroy();
|
||||||
window.close();
|
window.close();
|
||||||
setShowingSave(false);
|
setShowingSave(false);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export type EditEvent =
|
|||||||
| "rename"
|
| "rename"
|
||||||
| "updateTree"
|
| "updateTree"
|
||||||
| "updateNode"
|
| "updateNode"
|
||||||
|
| "searchNode"
|
||||||
| "editSubtree"
|
| "editSubtree"
|
||||||
| "saveAsSubtree";
|
| "saveAsSubtree";
|
||||||
|
|
||||||
@@ -53,7 +54,7 @@ export class EditorStore {
|
|||||||
graphMatrix?: Matrix;
|
graphMatrix?: Matrix;
|
||||||
graph!: TreeGraph;
|
graph!: TreeGraph;
|
||||||
|
|
||||||
dispatch?: (event: EditEvent, data?: unknown) => void;
|
dispatch!: (event: EditEvent, data?: unknown) => void;
|
||||||
|
|
||||||
constructor(path: string) {
|
constructor(path: string) {
|
||||||
this.path = path;
|
this.path = path;
|
||||||
@@ -79,15 +80,6 @@ export type FileTreeType = {
|
|||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type NodeTreeType = {
|
|
||||||
title: string;
|
|
||||||
def?: NodeDef;
|
|
||||||
icon?: React.ReactNode;
|
|
||||||
isLeaf?: boolean;
|
|
||||||
children?: NodeTreeType[];
|
|
||||||
style?: React.CSSProperties;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type EditNode = {
|
export type EditNode = {
|
||||||
data: NodeModel;
|
data: NodeModel;
|
||||||
editable: boolean;
|
editable: boolean;
|
||||||
@@ -112,7 +104,6 @@ export type WorkspaceStore = {
|
|||||||
path: string;
|
path: string;
|
||||||
|
|
||||||
allFiles: string[];
|
allFiles: string[];
|
||||||
nodeTree?: NodeTreeType;
|
|
||||||
fileTree?: FileTreeType;
|
fileTree?: FileTreeType;
|
||||||
editors: EditorStore[];
|
editors: EditorStore[];
|
||||||
editing?: EditorStore;
|
editing?: EditorStore;
|
||||||
@@ -191,7 +182,7 @@ const loadFileTree = (workdir: string, filename: string) => {
|
|||||||
|
|
||||||
const saveFile = (editor?: EditorStore) => {
|
const saveFile = (editor?: EditorStore) => {
|
||||||
if (editor?.unsave) {
|
if (editor?.unsave) {
|
||||||
editor.dispatch?.("save");
|
editor.dispatch("save");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -260,7 +251,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
}
|
}
|
||||||
if (buildDir) {
|
if (buildDir) {
|
||||||
for (const editor of workspace.editors) {
|
for (const editor of workspace.editors) {
|
||||||
editor.dispatch?.("save");
|
editor.dispatch("save");
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
@@ -452,37 +443,12 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
loadNodeDefs: () => {
|
loadNodeDefs: () => {
|
||||||
const workspace = get();
|
const workspace = get();
|
||||||
const nodeDefData = readJson(`${workspace.workdir}/node-config.b3-setting`) as NodeDef[];
|
const nodeDefData = readJson(`${workspace.workdir}/node-config.b3-setting`) as NodeDef[];
|
||||||
const nodeTree: NodeTreeType = {
|
|
||||||
title: i18n.t("nodeDefinition"),
|
|
||||||
children: [],
|
|
||||||
style: {
|
|
||||||
fontWeight: "bold",
|
|
||||||
fontSize: "13px",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const nodeDefs: Map<string, NodeDef> = new Map();
|
const nodeDefs: Map<string, NodeDef> = new Map();
|
||||||
for (const v of nodeDefData) {
|
for (const v of nodeDefData) {
|
||||||
nodeDefs.set(v.name, v);
|
nodeDefs.set(v.name, v);
|
||||||
let catalog = nodeTree.children?.find((nt) => nt.title === v.type);
|
|
||||||
if (!catalog) {
|
|
||||||
catalog = {
|
|
||||||
title: v.type,
|
|
||||||
children: [],
|
|
||||||
};
|
|
||||||
nodeTree.children?.push(catalog);
|
|
||||||
}
|
}
|
||||||
catalog.children?.push({
|
set({ nodeDefs });
|
||||||
title: `${v.name}(${v.desc})`,
|
workspace.editing?.dispatch("reload");
|
||||||
isLeaf: true,
|
|
||||||
def: v,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
nodeTree.children?.sort((a, b) => a.title.localeCompare(b.title));
|
|
||||||
nodeTree.children?.forEach((child) =>
|
|
||||||
child.children?.sort((a, b) => a.title.localeCompare(b.title))
|
|
||||||
);
|
|
||||||
set({ nodeDefs, nodeTree });
|
|
||||||
workspace.editing?.dispatch?.("reload");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// node edit
|
// node edit
|
||||||
|
|||||||
+17
-1
@@ -1,6 +1,7 @@
|
|||||||
import { TreeGraphData as G6TreeGraphData } from "@antv/g6";
|
import { TreeGraphData as G6TreeGraphData } from "@antv/g6";
|
||||||
|
|
||||||
export type NodeArgType = "string" | "int" | "float" | "boolean" | "enum" | "code";
|
export type NodeArgType = "string" | "int" | "float" | "boolean" | "enum" | "code";
|
||||||
|
export type NodeType = "Action" | "Composite" | "Decorator" | "Condition" | "Other" | "Error";
|
||||||
|
|
||||||
export interface NodeArgOption {
|
export interface NodeArgOption {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -16,7 +17,7 @@ export interface NodeArg {
|
|||||||
}
|
}
|
||||||
export interface NodeDef {
|
export interface NodeDef {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: NodeType;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
args?: NodeArg[];
|
args?: NodeArg[];
|
||||||
input?: string[];
|
input?: string[];
|
||||||
@@ -67,3 +68,18 @@ export const unknownNodeDef: NodeDef = {
|
|||||||
desc: "新建节点",
|
desc: "新建节点",
|
||||||
type: "Action",
|
type: "Action",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getNodeType = (def: NodeDef): NodeType => {
|
||||||
|
const type = def.type.toLocaleLowerCase().toString();
|
||||||
|
if (type.startsWith("action")) {
|
||||||
|
return "Action";
|
||||||
|
} else if (type.startsWith("composite")) {
|
||||||
|
return "Composite";
|
||||||
|
} else if (type.startsWith("decorator")) {
|
||||||
|
return "Decorator";
|
||||||
|
} else if (type.startsWith("condition")) {
|
||||||
|
return "Condition";
|
||||||
|
} else {
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
};
|
||||||
+28
-10
@@ -368,24 +368,42 @@ export const createNewTree = (filename: string) => {
|
|||||||
return tree;
|
return tree;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toBreakWord = (str: string, len: number, char = "\n") => {
|
const isAsciiChar = (c: number) => {
|
||||||
let strTemp = "";
|
return (c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toBreakWord = (str: string, maxlen: number, char = "\n") => {
|
||||||
|
const chars: string[] = [];
|
||||||
let line = 1;
|
let line = 1;
|
||||||
if (str.length <= len) {
|
let len = 0;
|
||||||
return { str, line };
|
for (let i = 0; i < str.length; i++) {
|
||||||
}
|
const c = str.charCodeAt(i);
|
||||||
while (str.length > len) {
|
len += isAsciiChar(c) ? 1 : 2;
|
||||||
strTemp += str.slice(0, len) + char;
|
chars.push(String.fromCharCode(c));
|
||||||
str = str.slice(len, str.length);
|
if (len >= maxlen) {
|
||||||
|
len = 0;
|
||||||
line++;
|
line++;
|
||||||
|
chars.push("\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
strTemp += str;
|
|
||||||
return {
|
return {
|
||||||
str: strTemp,
|
str: chars.join(""),
|
||||||
line,
|
line,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const cutWordTo = (str: string, maxlen: number) => {
|
||||||
|
let i = 0;
|
||||||
|
for (; i < str.length; i++) {
|
||||||
|
const c = str.charCodeAt(i);
|
||||||
|
maxlen -= isAsciiChar(c) ? 1 : 2;
|
||||||
|
if (maxlen <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.slice(0, i) + (i < str.length - 1 ? "..." : "");
|
||||||
|
};
|
||||||
|
|
||||||
export const createProject = (path: string) => {
|
export const createProject = (path: string) => {
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
Path.dirname(path) + "/node-config.b3-setting",
|
Path.dirname(path) + "/node-config.b3-setting",
|
||||||
|
|||||||
+2
-1
@@ -57,7 +57,8 @@ export const Hotkey = {
|
|||||||
Redo: isMacos ? hotkey("shift+ctrl+z") : hotkey("ctrl+y"),
|
Redo: isMacos ? hotkey("shift+ctrl+z") : hotkey("ctrl+y"),
|
||||||
Replace: hotkey("shift+ctrl+v"),
|
Replace: hotkey("shift+ctrl+v"),
|
||||||
Save: hotkey("ctrl+s"),
|
Save: hotkey("ctrl+s"),
|
||||||
Search: hotkey("ctrl+p"),
|
SearchTree: hotkey("ctrl+p"),
|
||||||
|
SearchNode: hotkey("ctrl+f"),
|
||||||
Undo: hotkey("ctrl+z"),
|
Undo: hotkey("ctrl+z"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user