fix node layout error and support subtree
This commit is contained in:
1 parent
d3c5bf53e7
commit
258a006383
21 files changed
+6000
-11438
No files matched your search
@@ -1,3 +1,3 @@
|
|||||||
registry=https://registry.npm.taobao.org/
|
# registry=https://registry.npm.taobao.org/
|
||||||
ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
|
# ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/
|
||||||
ELECTRON_BUILDER_BINARIES_MIRROR=https://registry.npmmirror.com/-/binary/electron-builder-binaries/
|
# ELECTRON_BUILDER_BINARIES_MIRROR=https://registry.npmmirror.com/-/binary/electron-builder-binaries/
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"folders": [
|
||||||
|
{
|
||||||
|
"path": "."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
|
"[json]": {
|
||||||
|
"editor.formatOnSave": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { TreeGraphData } from "@antv/g6/lib/types";
|
import { TreeGraphData } from "@antv/g6/lib/types";
|
||||||
|
|
||||||
export interface ArgsOptional{
|
export interface ArgsOptional {
|
||||||
name:string;
|
name: string;
|
||||||
value:string|number;
|
value: string | number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ArgsDefType {
|
export interface ArgsDefType {
|
||||||
@@ -31,6 +31,7 @@ export interface BehaviorNodeModel {
|
|||||||
output?: string[];
|
output?: string[];
|
||||||
children?: BehaviorNodeModel[];
|
children?: BehaviorNodeModel[];
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
path?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BehaviorTreeModel {
|
export interface BehaviorTreeModel {
|
||||||
@@ -48,6 +49,8 @@ export interface GraphNodeModel extends TreeGraphData {
|
|||||||
children?: GraphNodeModel[];
|
children?: GraphNodeModel[];
|
||||||
conf: BehaviorNodeTypeModel;
|
conf: BehaviorNodeTypeModel;
|
||||||
debug?: boolean;
|
debug?: boolean;
|
||||||
|
parent?: string;
|
||||||
|
path?: string;
|
||||||
|
|
||||||
size?: number[];
|
size?: number[];
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ export default {
|
|||||||
COPY_NODE: "COPY_NODE",
|
COPY_NODE: "COPY_NODE",
|
||||||
PASTE_NODE: "PASTE_NODE",
|
PASTE_NODE: "PASTE_NODE",
|
||||||
SAVE_ALL: "SAVE_ALL",
|
SAVE_ALL: "SAVE_ALL",
|
||||||
|
BUILD: "BUILD",
|
||||||
SAVE: "SAVE",
|
SAVE: "SAVE",
|
||||||
UNDO: "UNDO",
|
UNDO: "UNDO",
|
||||||
REDO: "REDO",
|
REDO: "REDO",
|
||||||
|
|||||||
+92
-41
@@ -1,13 +1,27 @@
|
|||||||
import { TreeGraphData } from "@antv/g6/lib/types";
|
import { TreeGraph } from "@antv/g6";
|
||||||
import { BehaviorNodeModel, BehaviorTreeModel, GraphNodeModel } from "./BehaviorTreeModel";
|
|
||||||
import Settings from "../main-process/Settings";
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import Settings from "../main-process/Settings";
|
||||||
|
import { BehaviorNodeModel, BehaviorTreeModel, GraphNodeModel } from "./BehaviorTreeModel";
|
||||||
|
import { message } from "antd";
|
||||||
|
|
||||||
|
interface INodeDataLike {
|
||||||
|
path?: string;
|
||||||
|
id: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isSubtree = (nodeData: INodeDataLike) => {
|
||||||
|
return nodeData.path && nodeData.id.toString() !== "1";
|
||||||
|
};
|
||||||
|
|
||||||
|
const parsingStack: string[] = [];
|
||||||
|
|
||||||
export const cloneNodeData = (nodeData: GraphNodeModel) => {
|
export const cloneNodeData = (nodeData: GraphNodeModel) => {
|
||||||
const newData: BehaviorNodeModel = {
|
const newData: BehaviorNodeModel = {
|
||||||
id: Number(nodeData.id),
|
id: Number(nodeData.id),
|
||||||
name: nodeData.name,
|
name: nodeData.name,
|
||||||
desc: nodeData.desc,
|
desc: nodeData.desc,
|
||||||
|
path: nodeData.path,
|
||||||
};
|
};
|
||||||
if (nodeData.input) {
|
if (nodeData.input) {
|
||||||
newData.input = [];
|
newData.input = [];
|
||||||
@@ -28,8 +42,8 @@ export const cloneNodeData = (nodeData: GraphNodeModel) => {
|
|||||||
newData.args[k] = v;
|
newData.args[k] = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (nodeData.children) {
|
if (nodeData.children && !isSubtree(nodeData)) {
|
||||||
newData.children = []
|
newData.children = [];
|
||||||
for (let child of nodeData.children) {
|
for (let child of nodeData.children) {
|
||||||
newData.children.push(cloneNodeData(child));
|
newData.children.push(cloneNodeData(child));
|
||||||
}
|
}
|
||||||
@@ -44,6 +58,7 @@ export const refreshNodeId = (nodeData: GraphNodeModel, id?: number) => {
|
|||||||
nodeData.id = (id++).toString();
|
nodeData.id = (id++).toString();
|
||||||
if (nodeData.children) {
|
if (nodeData.children) {
|
||||||
nodeData.children.forEach((child) => {
|
nodeData.children.forEach((child) => {
|
||||||
|
child.parent = nodeData.id;
|
||||||
id = refreshNodeId(child, id);
|
id = refreshNodeId(child, id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -51,21 +66,24 @@ export const refreshNodeId = (nodeData: GraphNodeModel, id?: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const calcTreeNodeSize = (treeNode: GraphNodeModel) => {
|
export const calcTreeNodeSize = (treeNode: GraphNodeModel) => {
|
||||||
var height = 40;
|
var height = 40 + 2;
|
||||||
const updateHeight = (obj: any) => {
|
const updateHeight = (obj: any) => {
|
||||||
if (Array.isArray(obj) || (obj && Object.keys(obj).length > 0)) {
|
if (Array.isArray(obj) || (obj && Object.keys(obj).length > 0)) {
|
||||||
const { str, line } = toBreakWord(`参数:${JSON.stringify(obj)}`, 35);
|
const { str, line } = toBreakWord(`参数:${JSON.stringify(obj)}`, 35);
|
||||||
height += 20 * line;
|
height += 20 * line;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if (treeNode.path) {
|
||||||
|
height += 20;
|
||||||
|
}
|
||||||
updateHeight(treeNode.args);
|
updateHeight(treeNode.args);
|
||||||
updateHeight(treeNode.input);
|
updateHeight(treeNode.input);
|
||||||
updateHeight(treeNode.output);
|
updateHeight(treeNode.output);
|
||||||
return [200, height];
|
return [200, height];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings) => {
|
export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings, parent?: string) => {
|
||||||
const treeData: GraphNodeModel = {
|
let treeData: GraphNodeModel = {
|
||||||
id: bNode.id.toString(),
|
id: bNode.id.toString(),
|
||||||
name: bNode.name,
|
name: bNode.name,
|
||||||
desc: bNode.desc,
|
desc: bNode.desc,
|
||||||
@@ -74,19 +92,60 @@ export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings) =>
|
|||||||
output: bNode.output,
|
output: bNode.output,
|
||||||
debug: bNode.debug,
|
debug: bNode.debug,
|
||||||
conf: settings.getNodeConf(bNode.name),
|
conf: settings.getNodeConf(bNode.name),
|
||||||
|
parent: parent,
|
||||||
};
|
};
|
||||||
|
|
||||||
treeData.size = calcTreeNodeSize(treeData);
|
treeData.size = calcTreeNodeSize(treeData);
|
||||||
|
|
||||||
|
if (!parent) {
|
||||||
|
parsingStack.length = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (bNode.children) {
|
if (bNode.children) {
|
||||||
treeData.children = [];
|
treeData.children = [];
|
||||||
bNode.children.forEach((child) => {
|
bNode.children.forEach((child) => {
|
||||||
treeData.children.push(createTreeData(child, settings));
|
treeData.children.push(createTreeData(child, settings, treeData.id));
|
||||||
});
|
});
|
||||||
|
} else if (bNode.path) {
|
||||||
|
if (parsingStack.indexOf(bNode.path) >= 0) {
|
||||||
|
treeData.path = bNode.path;
|
||||||
|
treeData.size = calcTreeNodeSize(treeData);
|
||||||
|
message.error(`循环引用节点:${bNode.path}`, 4);
|
||||||
|
return treeData;
|
||||||
|
}
|
||||||
|
parsingStack.push(bNode.path);
|
||||||
|
try {
|
||||||
|
const subtreePath = settings.workdir + "/" + bNode.path;
|
||||||
|
const str = fs.readFileSync(subtreePath, "utf8");
|
||||||
|
console.log("read subtree:", subtreePath);
|
||||||
|
treeData = createTreeData(JSON.parse(str).root, settings, treeData.id);
|
||||||
|
treeData.path = bNode.path;
|
||||||
|
treeData.size = calcTreeNodeSize(treeData);
|
||||||
|
} catch (error) {
|
||||||
|
console.log("parse subtree:", error);
|
||||||
|
}
|
||||||
|
parsingStack.pop();
|
||||||
}
|
}
|
||||||
calcTreeNodeSize(treeData);
|
calcTreeNodeSize(treeData);
|
||||||
return treeData;
|
return treeData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createFileData = (gNode: GraphNodeModel) => {
|
export const createBuildData = (path: string, settings: Settings): BehaviorNodeModel | null => {
|
||||||
|
try {
|
||||||
|
path = settings.workdir + "/" + path;
|
||||||
|
const str = fs.readFileSync(path, "utf8");
|
||||||
|
let treeModel = JSON.parse(str);
|
||||||
|
const data = createTreeData(treeModel.root, settings);
|
||||||
|
refreshNodeId(data);
|
||||||
|
treeModel.root = createFileData(data, true);
|
||||||
|
return treeModel as BehaviorNodeModel;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("build error:", path, error);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createFileData = (gNode: GraphNodeModel, includeSubtree?: boolean) => {
|
||||||
const nodeData: BehaviorNodeModel = {
|
const nodeData: BehaviorNodeModel = {
|
||||||
id: Number(gNode.id),
|
id: Number(gNode.id),
|
||||||
name: gNode.name,
|
name: gNode.name,
|
||||||
@@ -95,45 +154,37 @@ export const createFileData = (gNode: GraphNodeModel) => {
|
|||||||
input: gNode.input || undefined,
|
input: gNode.input || undefined,
|
||||||
output: gNode.output || undefined,
|
output: gNode.output || undefined,
|
||||||
debug: gNode.debug,
|
debug: gNode.debug,
|
||||||
|
path: gNode.path,
|
||||||
};
|
};
|
||||||
if (gNode.children) {
|
if (gNode.children && (includeSubtree || !isSubtree(gNode))) {
|
||||||
nodeData.children = [];
|
nodeData.children = [];
|
||||||
gNode.children.forEach((child) => {
|
gNode.children.forEach((child) => {
|
||||||
nodeData.children.push(createFileData(child));
|
nodeData.children.push(createFileData(child, includeSubtree));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return nodeData;
|
return nodeData;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const findParent = (node: TreeGraphData, id: string): TreeGraphData | null => {
|
export const findParent = (graph: TreeGraph, node: GraphNodeModel) => {
|
||||||
if (node.children) {
|
if (node.parent) {
|
||||||
for (let child of node.children) {
|
return graph.findDataById(node.parent);
|
||||||
if (child.id == id) {
|
|
||||||
return node;
|
|
||||||
} else {
|
} else {
|
||||||
let parent = findParent(child, id);
|
|
||||||
if (parent) {
|
|
||||||
return parent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const findFromAllChildren = (node: TreeGraphData, id: string): TreeGraphData | null => {
|
export const isAncestor = (
|
||||||
if (node.id == id) {
|
graph: TreeGraph,
|
||||||
return node;
|
ancestor: GraphNodeModel,
|
||||||
|
node: GraphNodeModel
|
||||||
|
): boolean => {
|
||||||
|
if (ancestor.id === node.parent) {
|
||||||
|
return true;
|
||||||
|
} else if (node.parent) {
|
||||||
|
return isAncestor(graph, ancestor, graph.findDataById(node.parent) as GraphNodeModel);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (node.children) {
|
|
||||||
for (let child of node.children) {
|
|
||||||
let found = findFromAllChildren(child, id);
|
|
||||||
if (found) {
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getRemoteSettings = () => {
|
export const getRemoteSettings = () => {
|
||||||
@@ -156,20 +207,20 @@ export const createNewTree = (filename: string) => {
|
|||||||
return tree;
|
return tree;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toBreakWord = (str: string, len: number, char='\n') => {
|
export const toBreakWord = (str: string, len: number, char = "\n") => {
|
||||||
var strTemp = "";
|
var strTemp = "";
|
||||||
var line = 1;
|
var line = 1;
|
||||||
if(str.length <= len) {
|
if (str.length <= len) {
|
||||||
return {str, line};
|
return { str, line };
|
||||||
}
|
}
|
||||||
while (str.length > len) {
|
while (str.length > len) {
|
||||||
strTemp += str.substr(0, len) + char;
|
strTemp += str.substr(0, len) + char;
|
||||||
str = str.substr(len, str.length);
|
str = str.substr(len, str.length);
|
||||||
line ++;
|
line++;
|
||||||
}
|
}
|
||||||
strTemp += str;
|
strTemp += str;
|
||||||
return {
|
return {
|
||||||
str: strTemp,
|
str: strTemp,
|
||||||
line,
|
line,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
+32
-32
@@ -54,7 +54,7 @@ export default class AppMenu {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "新建",
|
label: "新建",
|
||||||
accelerator: "ctrl+n",
|
accelerator: "CommandOrControl+n",
|
||||||
click: () => {
|
click: () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const res = await dialog.showSaveDialog({
|
const res = await dialog.showSaveDialog({
|
||||||
@@ -74,7 +74,7 @@ export default class AppMenu {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "打开文件",
|
label: "打开文件",
|
||||||
accelerator: "Ctrl+O",
|
accelerator: "CommandOrControl+O",
|
||||||
click: () => {
|
click: () => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const res = await dialog.showOpenDialog({
|
const res = await dialog.showOpenDialog({
|
||||||
@@ -102,14 +102,14 @@ export default class AppMenu {
|
|||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "保存",
|
label: "保存",
|
||||||
accelerator: "ctrl+s",
|
accelerator: "CommandOrControl+s",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.SAVE);
|
this.webContents.send(MainEventType.SAVE);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "全部保存",
|
label: "全部保存",
|
||||||
accelerator: "ctrl+shift+s",
|
accelerator: "CommandOrControl+shift+s",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.SAVE_ALL);
|
this.webContents.send(MainEventType.SAVE_ALL);
|
||||||
},
|
},
|
||||||
@@ -136,6 +136,13 @@ export default class AppMenu {
|
|||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "构建",
|
||||||
|
accelerator: "CommandOrControl+b",
|
||||||
|
click: () => {
|
||||||
|
this.webContents.send(MainEventType.BUILD);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -146,29 +153,29 @@ export default class AppMenu {
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "撤销",
|
label: "撤销",
|
||||||
accelerator: "ctrl+z",
|
accelerator: "CommandOrControl+z",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.UNDO);
|
this.webContents.send(MainEventType.UNDO);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "恢复",
|
label: "恢复",
|
||||||
accelerator: "ctrl+y",
|
accelerator: "CommandOrControl+y",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.REDO);
|
this.webContents.send(MainEventType.REDO);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "新建节点",
|
label: "新建节点",
|
||||||
accelerator: "insert",
|
accelerator: process.platform === "darwin" ? "enter" : "insert",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.CREATE_NODE, "unknow");
|
this.webContents.send(MainEventType.CREATE_NODE, "unknow");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "删除节点",
|
label: "删除节点",
|
||||||
accelerator: "delete",
|
accelerator: process.platform === "darwin" ? "backspace" : "delete",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.DELETE_NODE);
|
this.webContents.send(MainEventType.DELETE_NODE);
|
||||||
},
|
},
|
||||||
@@ -176,21 +183,14 @@ export default class AppMenu {
|
|||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "复制节点",
|
label: "复制节点",
|
||||||
accelerator: "ctrl+c",
|
|
||||||
role: "copy",
|
role: "copy",
|
||||||
click: () => {
|
|
||||||
this.webContents.send(MainEventType.COPY_NODE);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "粘贴节点",
|
label: "粘贴节点",
|
||||||
accelerator: "ctrl+v",
|
role: "paste",
|
||||||
click: () => {
|
|
||||||
this.webContents.send(MainEventType.PASTE_NODE);
|
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
]
|
});
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private createWorkspaceMenu() {
|
private createWorkspaceMenu() {
|
||||||
@@ -277,7 +277,7 @@ export default class AppMenu {
|
|||||||
{ type: "separator" },
|
{ type: "separator" },
|
||||||
{
|
{
|
||||||
label: "打开目录",
|
label: "打开目录",
|
||||||
accelerator: "Ctrl+Shift+O",
|
accelerator: "CommandOrControl+Shift+O",
|
||||||
click: () => {
|
click: () => {
|
||||||
if (!this.settings.curWorkspace.getNodeConfPath()) {
|
if (!this.settings.curWorkspace.getNodeConfPath()) {
|
||||||
dialog.showErrorBox("警告", "请先指定节点定义配置!");
|
dialog.showErrorBox("警告", "请先指定节点定义配置!");
|
||||||
@@ -341,23 +341,23 @@ export default class AppMenu {
|
|||||||
for (let model of this.settings.curWorkspace.getServers()) {
|
for (let model of this.settings.curWorkspace.getServers()) {
|
||||||
serverItems.push({
|
serverItems.push({
|
||||||
label: `${model.name} ${model.host}`,
|
label: `${model.name} ${model.host}`,
|
||||||
type: 'checkbox',
|
type: "checkbox",
|
||||||
checked: curServerName == model.name,
|
checked: curServerName == model.name,
|
||||||
click: () => {
|
click: () => {
|
||||||
this.settings.serverName = model.name;
|
this.settings.serverName = model.name;
|
||||||
this.mainProcess.rebuildMenu();
|
this.mainProcess.rebuildMenu();
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
return new MenuItem({
|
return new MenuItem({
|
||||||
label: "开发工具",
|
label: "开发工具",
|
||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: "热更",
|
label: "热更",
|
||||||
accelerator: "Ctrl+R",
|
accelerator: "CommandOrControl+R",
|
||||||
click: () => {
|
click: () => {
|
||||||
this.webContents.send(MainEventType.RELOAD_SERVER);
|
this.webContents.send(MainEventType.RELOAD_SERVER);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "联调服务器",
|
label: "联调服务器",
|
||||||
@@ -375,14 +375,14 @@ export default class AppMenu {
|
|||||||
this.webContents.send(MainEventType.BATCH_EXEC, res.filePaths[0]);
|
this.webContents.send(MainEventType.BATCH_EXEC, res.filePaths[0]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: "separator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "打开控制台",
|
label: "打开控制台",
|
||||||
accelerator: "Ctrl+Shift+I",
|
accelerator: "CommandOrControl+Shift+I",
|
||||||
click: (_, browserWindow) => {
|
click: (_, browserWindow) => {
|
||||||
browserWindow.webContents.toggleDevTools();
|
browserWindow.webContents.toggleDevTools();
|
||||||
},
|
},
|
||||||
@@ -394,11 +394,11 @@ export default class AppMenu {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'separator',
|
type: "separator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: `当前版本:${packageConf.version}`
|
label: `当前版本:${packageConf.version}`,
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ export class MainProcess {
|
|||||||
nativeTheme.themeSource = "dark";
|
nativeTheme.themeSource = "dark";
|
||||||
app.on("ready", () => {
|
app.on("ready", () => {
|
||||||
this.createWindow();
|
this.createWindow();
|
||||||
electronLocalshortcut.register(this.mainWindow, "Ctrl+C", () => {
|
electronLocalshortcut.register(this.mainWindow, "CommandOrControl+C", () => {
|
||||||
this.mainWindow.webContents.send(MainEventType.COPY_NODE);
|
this.mainWindow.webContents.send(MainEventType.COPY_NODE);
|
||||||
});
|
});
|
||||||
electronLocalshortcut.register(this.mainWindow, "Ctrl+V", () => {
|
electronLocalshortcut.register(this.mainWindow, "CommandOrControl+V", () => {
|
||||||
this.mainWindow.webContents.send(MainEventType.PASTE_NODE);
|
this.mainWindow.webContents.send(MainEventType.PASTE_NODE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -46,7 +46,7 @@ export class MainProcess {
|
|||||||
height: 800,
|
height: 800,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation:false,
|
contextIsolation: false,
|
||||||
},
|
},
|
||||||
// fullscreenable:false,
|
// fullscreenable:false,
|
||||||
// maximizable:false
|
// maximizable:false
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import Workspace from "./Workspace";
|
import Workspace from "./Workspace";
|
||||||
|
import { app } from "electron";
|
||||||
|
|
||||||
export interface BehaviorNodeClassify {
|
export interface BehaviorNodeClassify {
|
||||||
classify: string;
|
classify: string;
|
||||||
@@ -14,7 +15,10 @@ export interface SettingsModel {
|
|||||||
treesDesc?: { [name: string]: string };
|
treesDesc?: { [name: string]: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingPath = "settings.json";
|
const settingPath = app.getPath("userData") + "/settings.json";
|
||||||
|
|
||||||
|
console.log("setting path:", settingPath);
|
||||||
|
|
||||||
const sampleNodeClassify: BehaviorNodeClassify[] = [
|
const sampleNodeClassify: BehaviorNodeClassify[] = [
|
||||||
{ classify: "Composite", desc: "复合节点" },
|
{ classify: "Composite", desc: "复合节点" },
|
||||||
{ classify: "Decorator", desc: "修饰节点" },
|
{ classify: "Decorator", desc: "修饰节点" },
|
||||||
@@ -35,7 +39,7 @@ export default class Settings {
|
|||||||
fs.writeFileSync(settingPath, JSON.stringify(this.model, null, 2));
|
fs.writeFileSync(settingPath, JSON.stringify(this.model, null, 2));
|
||||||
this.dirty = false;
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
}, 1000)
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
get nodeConfPath() {
|
get nodeConfPath() {
|
||||||
@@ -54,7 +58,7 @@ export default class Settings {
|
|||||||
return this.model.nodeClassify;
|
return this.model.nodeClassify;
|
||||||
}
|
}
|
||||||
get serverModel() {
|
get serverModel() {
|
||||||
const servers = this.curWorkspace.getServers()
|
const servers = this.curWorkspace.getServers();
|
||||||
if (!servers) {
|
if (!servers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -66,7 +70,7 @@ export default class Settings {
|
|||||||
return servers[0];
|
return servers[0];
|
||||||
}
|
}
|
||||||
get serverName() {
|
get serverName() {
|
||||||
const servers = this.curWorkspace.getServers()
|
const servers = this.curWorkspace.getServers();
|
||||||
if (!servers) {
|
if (!servers) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -78,7 +82,7 @@ export default class Settings {
|
|||||||
if (servers[0]) {
|
if (servers[0]) {
|
||||||
return servers[0].name;
|
return servers[0].name;
|
||||||
} else {
|
} else {
|
||||||
return ''
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set serverName(name: string) {
|
set serverName(name: string) {
|
||||||
@@ -119,10 +123,18 @@ export default class Settings {
|
|||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get workdir() {
|
||||||
|
return this.curWorkspace.getWorkdir();
|
||||||
|
}
|
||||||
|
|
||||||
getNodeConf(name: string) {
|
getNodeConf(name: string) {
|
||||||
return this.curWorkspace.getNodeConf(name);
|
return this.curWorkspace.getNodeConf(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasNodeConf(name: string) {
|
||||||
|
return this.curWorkspace.hasNodeConf(name);
|
||||||
|
}
|
||||||
|
|
||||||
pushRecentWorkspace(path: string) {
|
pushRecentWorkspace(path: string) {
|
||||||
var list = this.model.recentWorkspaces;
|
var list = this.model.recentWorkspaces;
|
||||||
if (list.indexOf(path) >= 0) {
|
if (list.indexOf(path) >= 0) {
|
||||||
@@ -137,12 +149,12 @@ export default class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTreeDesc(name: string) {
|
getTreeDesc(name: string) {
|
||||||
const key = this.curWorkspace.getFilepath() + ' ' + name;
|
const key = this.curWorkspace.getFilepath() + " " + name;
|
||||||
var desc = this.model.treesDesc[key];
|
var desc = this.model.treesDesc[key];
|
||||||
if (!desc) {
|
if (!desc) {
|
||||||
const str = fs.readFileSync(name, "utf8");
|
const str = fs.readFileSync(name, "utf8");
|
||||||
const tree = JSON.parse(str);
|
const tree = JSON.parse(str);
|
||||||
desc = tree.desc ? tree.desc : '';
|
desc = tree.desc ? tree.desc : "";
|
||||||
this.model.treesDesc[key] = desc;
|
this.model.treesDesc[key] = desc;
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
@@ -150,7 +162,7 @@ export default class Settings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setTreeDesc(name: string, desc?: string) {
|
setTreeDesc(name: string, desc?: string) {
|
||||||
const key = this.curWorkspace.getFilepath() + ' ' + name;
|
const key = this.curWorkspace.getFilepath() + " " + name;
|
||||||
this.model.treesDesc[key] = desc;
|
this.model.treesDesc[key] = desc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,8 @@ export default class Workspace {
|
|||||||
const model = JSON.parse(str) as WorkspaceModel;
|
const model = JSON.parse(str) as WorkspaceModel;
|
||||||
if (model.isRelative) {
|
if (model.isRelative) {
|
||||||
const root = path.dirname(this.filepath);
|
const root = path.dirname(this.filepath);
|
||||||
this.nodeConfPath = path.join(root,model.nodeConfPath);
|
this.nodeConfPath = path.join(root, model.nodeConfPath);
|
||||||
this.workdir = path.join(root,model.workdir);
|
this.workdir = path.join(root, model.workdir);
|
||||||
} else {
|
} else {
|
||||||
this.nodeConfPath = model.nodeConfPath;
|
this.nodeConfPath = model.nodeConfPath;
|
||||||
this.workdir = model.workdir;
|
this.workdir = model.workdir;
|
||||||
@@ -86,7 +86,7 @@ export default class Workspace {
|
|||||||
this.workdir = workdir;
|
this.workdir = workdir;
|
||||||
}
|
}
|
||||||
getServers() {
|
getServers() {
|
||||||
if(this.model && this.model.servers) {
|
if (this.model && this.model.servers) {
|
||||||
return this.model.servers;
|
return this.model.servers;
|
||||||
} else {
|
} else {
|
||||||
return [];
|
return [];
|
||||||
@@ -154,4 +154,8 @@ export default class Workspace {
|
|||||||
getNodeConf(name: string) {
|
getNodeConf(name: string) {
|
||||||
return this.name2conf[name] || unknowNodeType;
|
return this.name2conf[name] || unknowNodeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasNodeConf(name: string) {
|
||||||
|
return !!this.name2conf[name];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Generated
+5396
-11203
File diff suppressed because it is too large.
Load diff
+22
-20
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "behavior3editor",
|
"name": "behavior3editor",
|
||||||
"version": "0.1.11",
|
"version": "0.2.0",
|
||||||
"description": "行为树编辑器",
|
"description": "行为树编辑器",
|
||||||
"main": "./dist/main.js",
|
"main": "./dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prestart": "webpack --config webpack.config.js",
|
"prestart": "webpack --config webpack.config.js",
|
||||||
"start": "electron .",
|
"start": "electron .",
|
||||||
"build": "electron-packager ./ behavior3editor --platform=win32 --arch=x64 --out=./build --overwrite",
|
"build": "electron-packager ./ behavior3editor --platform=win32 --arch=x64 --out=./build --overwrite",
|
||||||
|
"build-mac": "electron-packager ./ behavior3editor --platform=darwin --arch=universal --out=./build --overwrite",
|
||||||
"dist": "npm run prestart && electron-builder --dir && electron-builder --win --x64"
|
"dist": "npm run prestart && electron-builder --dir && electron-builder --win --x64"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -26,30 +27,31 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/zhandouxiaojiji/behavior3editor#readme",
|
"homepage": "https://github.com/zhandouxiaojiji/behavior3editor#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/electron-localshortcut": "^3.1.0",
|
"@types/electron-localshortcut": "^3.1.3",
|
||||||
"@types/node": "^14.14.8",
|
"@types/glob": "^8.1.0",
|
||||||
"@types/rimraf": "^3.0.0",
|
"@types/node": "^20.11.25",
|
||||||
"copy-webpack-plugin": "^6.3.1",
|
"@types/rimraf": "^3.0.2",
|
||||||
"css-loader": "^4.3.0",
|
"copy-webpack-plugin": "^12.0.2",
|
||||||
"electron": "^20.0.3",
|
"css-loader": "^6.10.0",
|
||||||
|
"electron": "^29.1.1",
|
||||||
|
"electron-builder": "^24.13.3",
|
||||||
"electron-localshortcut": "^3.2.1",
|
"electron-localshortcut": "^3.2.1",
|
||||||
"electron-packager": "^15.5.2",
|
"electron-packager": "^17.1.2",
|
||||||
"electron-builder": "^23.3.3",
|
"file-loader": "^6.2.0",
|
||||||
"file-loader": "^6.1.1",
|
"less": "^4.2.0",
|
||||||
"less": "^3.12.2",
|
"less-loader": "^12.2.0",
|
||||||
"less-loader": "^7.1.0",
|
"node-fetch": "^3.3.2",
|
||||||
"node-fetch": "^2.6.1",
|
"source-map-loader": "^5.0.0",
|
||||||
"source-map-loader": "^1.1.0",
|
"style-loader": "^3.3.4",
|
||||||
"style-loader": "^2.0.0",
|
"ts-loader": "^9.5.1",
|
||||||
"ts-loader": "^8.0.4",
|
"typescript": "^5.3.3",
|
||||||
"typescript": "^4.7.4",
|
"webpack": "^5.90.3",
|
||||||
"webpack": "^4.44.2",
|
"webpack-cli": "^5.1.4"
|
||||||
"webpack-cli": "^3.3.12"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^4.3.0",
|
"@ant-design/icons": "^4.3.0",
|
||||||
"@antv/g6": "^3.8.5",
|
"@antv/g6": "^3.8.5",
|
||||||
"@electron/remote": "^2.0.8",
|
"@electron/remote": "^2.1.2",
|
||||||
"@types/react": "^16.9.51",
|
"@types/react": "^16.9.51",
|
||||||
"@types/react-dom": "^16.9.8",
|
"@types/react-dom": "^16.9.8",
|
||||||
"antd": "^4.6.6",
|
"antd": "^4.6.6",
|
||||||
|
|||||||
@@ -207,20 +207,32 @@ export default class NodePanel extends React.Component<NodePanelProps> {
|
|||||||
const required = e.type.indexOf("?") == -1;
|
const required = e.type.indexOf("?") == -1;
|
||||||
if (e.type.indexOf("string") >= 0) {
|
if (e.type.indexOf("string") >= 0) {
|
||||||
return <Input onBlur={this.handleSubmit} />;
|
return <Input onBlur={this.handleSubmit} />;
|
||||||
} else if (e.type.indexOf("int") >= 0 || e.type.indexOf("float") >= 0) {
|
} else if (e.type.indexOf("int") >= 0) {
|
||||||
|
return (
|
||||||
|
<InputNumber
|
||||||
|
precision={0}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
onBlur={this.handleSubmit}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (e.type.indexOf("float") >= 0) {
|
||||||
return <InputNumber style={{ width: "100%" }} onBlur={this.handleSubmit} />;
|
return <InputNumber style={{ width: "100%" }} onBlur={this.handleSubmit} />;
|
||||||
} else if (e.type.indexOf("boolean") >= 0) {
|
} else if (e.type.indexOf("boolean") >= 0) {
|
||||||
return <Switch onChange={this.handleSubmit} />;
|
return <Switch onChange={this.handleSubmit} />;
|
||||||
} else if (e.type.indexOf("code") >= 0) {
|
} else if (e.type.indexOf("code") >= 0) {
|
||||||
return <Input onBlur={this.handleSubmit} placeholder={"表达式"} />;
|
return <Input onBlur={this.handleSubmit} placeholder={"表达式"} />;
|
||||||
} else if (e.type.indexOf("enum") >= 0 ) {
|
} else if (e.type.indexOf("enum") >= 0) {
|
||||||
return <Select style={{ width: 120 }} onChange={this.handleSubmit} >
|
return (
|
||||||
{
|
<Select style={{ width: 120 }} onChange={this.handleSubmit}>
|
||||||
e.options.map((e)=>{
|
{e.options.map((e) => {
|
||||||
return (<Option key={e.name} value={e.value}>{e.name}</Option>)
|
return (
|
||||||
})
|
<Option key={e.name} value={e.value}>
|
||||||
}
|
{e.name}
|
||||||
</Select>;
|
</Option>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+105
-46
@@ -1,22 +1,22 @@
|
|||||||
import * as React from "react";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import * as path from "path";
|
|
||||||
import { Row, Col, message, Card } from "antd";
|
|
||||||
import NodePanel from "./NodePanel";
|
|
||||||
import G6, { TreeGraph } from "@antv/g6";
|
import G6, { TreeGraph } from "@antv/g6";
|
||||||
import { G6GraphEvent } from "@antv/g6/lib/interface/behavior";
|
import { G6GraphEvent } from "@antv/g6/lib/interface/behavior";
|
||||||
import * as Utils from "../../common/Utils";
|
import { Col, Row, message } from "antd";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as React from "react";
|
||||||
import {
|
import {
|
||||||
|
BehaviorNodeModel,
|
||||||
BehaviorTreeModel,
|
BehaviorTreeModel,
|
||||||
GraphNodeModel,
|
GraphNodeModel,
|
||||||
BehaviorNodeModel,
|
|
||||||
} from "../../common/BehaviorTreeModel";
|
} from "../../common/BehaviorTreeModel";
|
||||||
import TreePanel from "./TreePanel";
|
import * as Utils from "../../common/Utils";
|
||||||
import Settings from "../../main-process/Settings";
|
import Settings from "../../main-process/Settings";
|
||||||
|
import NodePanel from "./NodePanel";
|
||||||
|
import TreePanel from "./TreePanel";
|
||||||
|
|
||||||
import "./Editor.css";
|
import { Matrix } from "@antv/g6/lib/types";
|
||||||
import { clipboard } from "electron";
|
import { clipboard } from "electron";
|
||||||
import { Matrix } from '@antv/g6/lib/types';
|
import "./Editor.css";
|
||||||
|
|
||||||
export interface EditorProps {
|
export interface EditorProps {
|
||||||
filepath: string;
|
filepath: string;
|
||||||
@@ -26,7 +26,7 @@ export interface EditorProps {
|
|||||||
interface EditorState {
|
interface EditorState {
|
||||||
curNodeId?: string;
|
curNodeId?: string;
|
||||||
blockNodeSelectChange?: boolean;
|
blockNodeSelectChange?: boolean;
|
||||||
viewportMatrix?: Matrix
|
viewportMatrix?: Matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Editor extends React.Component<EditorProps, EditorState> {
|
export default class Editor extends React.Component<EditorProps, EditorState> {
|
||||||
@@ -34,6 +34,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
state: EditorState = {};
|
state: EditorState = {};
|
||||||
|
|
||||||
private graph: TreeGraph;
|
private graph: TreeGraph;
|
||||||
|
private isInEditor: boolean = false;
|
||||||
private dragSrcId: string;
|
private dragSrcId: string;
|
||||||
private dragDstId: string;
|
private dragDstId: string;
|
||||||
private autoId: number;
|
private autoId: number;
|
||||||
@@ -56,7 +57,9 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps: EditorProps, nextState: EditorState) {
|
shouldComponentUpdate(nextProps: EditorProps, nextState: EditorState) {
|
||||||
return this.props.filepath != nextProps.filepath || this.state.curNodeId != nextState.curNodeId;
|
return (
|
||||||
|
this.props.filepath != nextProps.filepath || this.state.curNodeId != nextState.curNodeId
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@@ -118,15 +121,23 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
graph.on("viewportchange", (data: any) => {
|
graph.on("viewportchange", (data: any) => {
|
||||||
if (data.action == 'translate' || data.action == 'zoom') {
|
if (data.action == "translate" || data.action == "zoom") {
|
||||||
this.state.viewportMatrix = data.matrix
|
this.state.viewportMatrix = data.matrix;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
graph.on("contextmenu", (e: G6GraphEvent) => {
|
graph.on("contextmenu", (e: G6GraphEvent) => {
|
||||||
require("@electron/remote").Menu.getApplicationMenu().popup();
|
require("@electron/remote").Menu.getApplicationMenu().popup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
graph.on("canvas:mouseover", () => {
|
||||||
|
this.isInEditor = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
graph.on("canvas:mouseleave", () => {
|
||||||
|
this.isInEditor = false;
|
||||||
|
});
|
||||||
|
|
||||||
graph.on("node:mouseenter", (e: G6GraphEvent) => {
|
graph.on("node:mouseenter", (e: G6GraphEvent) => {
|
||||||
const { item } = e;
|
const { item } = e;
|
||||||
if (item.hasState("selected")) {
|
if (item.hasState("selected")) {
|
||||||
@@ -146,7 +157,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
graph.on("nodeselectchange", (e: G6GraphEvent) => {
|
graph.on("nodeselectchange", (e: G6GraphEvent) => {
|
||||||
if (this.state.blockNodeSelectChange) {
|
if (this.state.blockNodeSelectChange) {
|
||||||
// ** 重置选中效果
|
// ** 重置选中效果
|
||||||
this.onSelectNode(this.state.curNodeId)
|
this.onSelectNode(this.state.curNodeId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (e.target) {
|
if (e.target) {
|
||||||
@@ -237,16 +248,16 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rootData = graph.findDataById("1");
|
const rootData = graph.findDataById("1");
|
||||||
const srcData = graph.findDataById(srcNodeId);
|
const srcData = graph.findDataById(srcNodeId) as GraphNodeModel;
|
||||||
const srcParent = Utils.findParent(rootData, srcNodeId);
|
const srcParent = Utils.findParent(this.graph, srcData);
|
||||||
const dstData = graph.findDataById(dstNode.getID());
|
const dstData = graph.findDataById(dstNode.getID()) as GraphNodeModel;
|
||||||
const dstParent = Utils.findParent(rootData, dstNode.getID());
|
const dstParent = Utils.findParent(this.graph, dstData);
|
||||||
if (!srcParent) {
|
if (!srcParent) {
|
||||||
console.log("no parent!");
|
console.log("no parent!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Utils.findFromAllChildren(srcData, dstData.id)) {
|
if (Utils.isAncestor(this.graph, srcData, dstData)) {
|
||||||
// 不能将父节点拖到自已的子孙节点
|
// 不能将父节点拖到自已的子孙节点
|
||||||
console.log("cannot move to child");
|
console.log("cannot move to child");
|
||||||
return;
|
return;
|
||||||
@@ -262,6 +273,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
if (!dstData.children) {
|
if (!dstData.children) {
|
||||||
dstData.children = [];
|
dstData.children = [];
|
||||||
}
|
}
|
||||||
|
srcData.parent = dstData.id;
|
||||||
dstData.children.push(srcData);
|
dstData.children.push(srcData);
|
||||||
} else if (dragDir == "dragUp") {
|
} else if (dragDir == "dragUp") {
|
||||||
if (!dstParent) {
|
if (!dstParent) {
|
||||||
@@ -269,6 +281,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
removeSrc();
|
removeSrc();
|
||||||
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
|
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
|
||||||
|
srcData.parent = dstParent.id;
|
||||||
dstParent.children.splice(idx, 0, srcData);
|
dstParent.children.splice(idx, 0, srcData);
|
||||||
} else if (dragDir == "dragDown") {
|
} else if (dragDir == "dragDown") {
|
||||||
if (!dstParent) {
|
if (!dstParent) {
|
||||||
@@ -276,6 +289,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
removeSrc();
|
removeSrc();
|
||||||
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
|
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
|
||||||
|
srcData.parent = dstParent.id;
|
||||||
dstParent.children.splice(idx + 1, 0, srcData);
|
dstParent.children.splice(idx + 1, 0, srcData);
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@@ -309,6 +323,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
|
|
||||||
if (this.state.curNodeId) {
|
if (this.state.curNodeId) {
|
||||||
graph.setItemState(this.state.curNodeId, "selected", false);
|
graph.setItemState(this.state.curNodeId, "selected", false);
|
||||||
|
graph.setItemState(this.state.curNodeId, "hover", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ curNodeId });
|
this.setState({ curNodeId });
|
||||||
@@ -318,6 +333,10 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
createNode(name: string) {
|
createNode(name: string) {
|
||||||
|
if (!this.isInEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("editor create node", name);
|
console.log("editor create node", name);
|
||||||
const { curNodeId } = this.state;
|
const { curNodeId } = this.state;
|
||||||
if (!curNodeId) {
|
if (!curNodeId) {
|
||||||
@@ -333,7 +352,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
if (!curNodeData.children) {
|
if (!curNodeData.children) {
|
||||||
curNodeData.children = [];
|
curNodeData.children = [];
|
||||||
}
|
}
|
||||||
curNodeData.children.push(Utils.createTreeData(newNodeData, this.settings));
|
curNodeData.children.push(Utils.createTreeData(newNodeData, this.settings, curNodeId));
|
||||||
this.changeWithoutAnim();
|
this.changeWithoutAnim();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,15 +363,17 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (curNodeId == "1") {
|
if (curNodeId === "1") {
|
||||||
message.warn("根节点不能删除!");
|
message.warn("根节点不能删除!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.onSelectNode(null);
|
this.onSelectNode(null);
|
||||||
this.pushUndoStack();
|
this.pushUndoStack();
|
||||||
const rootData = this.graph.findDataById("1");
|
const parentData = Utils.findParent(
|
||||||
const parentData = Utils.findParent(rootData, curNodeId);
|
this.graph,
|
||||||
|
this.graph.findDataById(curNodeId) as GraphNodeModel
|
||||||
|
);
|
||||||
parentData.children = parentData.children.filter((e) => e.id != curNodeId);
|
parentData.children = parentData.children.filter((e) => e.id != curNodeId);
|
||||||
this.changeWithoutAnim();
|
this.changeWithoutAnim();
|
||||||
}
|
}
|
||||||
@@ -380,20 +401,35 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
root,
|
root,
|
||||||
desc: this.treeModel.desc,
|
desc: this.treeModel.desc,
|
||||||
} as BehaviorTreeModel;
|
} as BehaviorTreeModel;
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(filepath, JSON.stringify(treeModel, null, 2));
|
||||||
filepath,
|
|
||||||
JSON.stringify(treeModel, null, 2)
|
|
||||||
);
|
|
||||||
this.props.onChangeSaveState(false);
|
this.props.onChangeSaveState(false);
|
||||||
this.unsave = false;
|
this.unsave = false;
|
||||||
|
|
||||||
|
const treeData = Utils.createTreeData(root, this.settings);
|
||||||
|
this.autoId = Utils.refreshNodeId(treeData);
|
||||||
|
|
||||||
this.graph.set("animate", false);
|
this.graph.set("animate", false);
|
||||||
this.graph.changeData(Utils.createTreeData(root, this.settings));
|
this.graph.changeData(treeData);
|
||||||
this.graph.layout();
|
this.graph.layout();
|
||||||
this.restoreViewport();
|
this.restoreViewport();
|
||||||
this.graph.set("animate", true);
|
this.graph.set("animate", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reload() {
|
||||||
|
if (!this.unsave) {
|
||||||
|
const data = this.graph.findDataById("1") as GraphNodeModel;
|
||||||
|
this.autoId = Utils.refreshNodeId(data);
|
||||||
|
const root = Utils.createFileData(data);
|
||||||
|
const treeData = Utils.createTreeData(root, this.settings);
|
||||||
|
this.autoId = Utils.refreshNodeId(treeData);
|
||||||
|
this.graph.set("animate", false);
|
||||||
|
this.graph.changeData(treeData);
|
||||||
|
this.graph.layout();
|
||||||
|
this.restoreViewport();
|
||||||
|
this.graph.set("animate", true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getTreeModel() {
|
getTreeModel() {
|
||||||
const { filepath } = this.props;
|
const { filepath } = this.props;
|
||||||
const data = this.graph.findDataById("1") as GraphNodeModel;
|
const data = this.graph.findDataById("1") as GraphNodeModel;
|
||||||
@@ -407,28 +443,40 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copyNode() {
|
copyNode() {
|
||||||
|
if (!this.isInEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
console.log("editor copy node");
|
console.log("editor copy node");
|
||||||
const { curNodeId } = this.state;
|
const { curNodeId } = this.state;
|
||||||
if (!curNodeId) {
|
if (!curNodeId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = this.graph.findDataById(curNodeId) as GraphNodeModel;
|
const data = this.graph.findDataById(curNodeId) as GraphNodeModel;
|
||||||
clipboard.writeText(JSON.stringify(Utils.cloneNodeData(data), null, 2));
|
const str = JSON.stringify(Utils.cloneNodeData(data), null, 2);
|
||||||
|
console.log("copy:", str);
|
||||||
|
clipboard.writeText(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteNode() {
|
pasteNode() {
|
||||||
|
if (!this.isInEditor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { curNodeId } = this.state;
|
const { curNodeId } = this.state;
|
||||||
if (!curNodeId) {
|
if (!curNodeId) {
|
||||||
message.warn("未选中节点");
|
message.warn("未选中节点");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const curNodeData = this.graph.findDataById(curNodeId);
|
const curNodeData = this.graph.findDataById(curNodeId);
|
||||||
try {
|
try {
|
||||||
const str = clipboard.readText();
|
const str = clipboard.readText();
|
||||||
if (!str || str == "") {
|
if (!str || str == "") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = Utils.createTreeData(JSON.parse(str), this.settings);
|
console.log("paste:", str);
|
||||||
|
const data = Utils.createTreeData(JSON.parse(str), this.settings, curNodeId);
|
||||||
this.autoId = Utils.refreshNodeId(data, this.autoId);
|
this.autoId = Utils.refreshNodeId(data, this.autoId);
|
||||||
this.onSelectNode(null);
|
this.onSelectNode(null);
|
||||||
if (!curNodeData.children) {
|
if (!curNodeData.children) {
|
||||||
@@ -446,7 +494,9 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
|
|
||||||
useStackData(data: BehaviorNodeModel) {
|
useStackData(data: BehaviorNodeModel) {
|
||||||
this.graph.set("animate", false);
|
this.graph.set("animate", false);
|
||||||
this.graph.changeData(Utils.createTreeData(data, this.settings));
|
this.data = Utils.createTreeData(data, this.settings);
|
||||||
|
this.autoId = Utils.refreshNodeId(this.data);
|
||||||
|
this.graph.changeData(this.data);
|
||||||
this.graph.layout();
|
this.graph.layout();
|
||||||
this.restoreViewport();
|
this.restoreViewport();
|
||||||
this.graph.set("animate", true);
|
this.graph.set("animate", true);
|
||||||
@@ -456,7 +506,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pushUndoStack(keepRedo?: boolean) {
|
pushUndoStack(keepRedo?: boolean) {
|
||||||
this.undoStack.push(Utils.cloneNodeData(this.graph.findDataById('1') as GraphNodeModel));
|
this.undoStack.push(Utils.cloneNodeData(this.graph.findDataById("1") as GraphNodeModel));
|
||||||
console.log("push undo", this.undoStack);
|
console.log("push undo", this.undoStack);
|
||||||
if (!keepRedo) {
|
if (!keepRedo) {
|
||||||
this.redoStack = [];
|
this.redoStack = [];
|
||||||
@@ -464,7 +514,7 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pushRedoStack() {
|
pushRedoStack() {
|
||||||
this.redoStack.push(Utils.cloneNodeData(this.graph.findDataById('1') as GraphNodeModel));
|
this.redoStack.push(Utils.cloneNodeData(this.graph.findDataById("1") as GraphNodeModel));
|
||||||
console.log("push redo", this.redoStack);
|
console.log("push redo", this.redoStack);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,22 +554,35 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
return (
|
return (
|
||||||
<div className="editor">
|
<div className="editor">
|
||||||
<Row className="editorBd">
|
<Row className="editorBd">
|
||||||
<Col span={18} className="editorContent" ref={this.ref} onMouseDownCapture={(event) => {
|
<Col
|
||||||
|
span={18}
|
||||||
|
className="editorContent"
|
||||||
|
ref={this.ref}
|
||||||
|
onMouseDownCapture={(event) => {
|
||||||
this.state.blockNodeSelectChange = false;
|
this.state.blockNodeSelectChange = false;
|
||||||
}} />
|
}}
|
||||||
<Col span={6} className="editorSidebar" onMouseDownCapture={(event) => {
|
/>
|
||||||
|
<Col
|
||||||
|
span={6}
|
||||||
|
className="editorSidebar"
|
||||||
|
onMouseDownCapture={(event) => {
|
||||||
this.state.blockNodeSelectChange = true;
|
this.state.blockNodeSelectChange = true;
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{curNode ? (
|
{curNode ? (
|
||||||
<NodePanel
|
<NodePanel
|
||||||
model={curNode}
|
model={curNode}
|
||||||
settings={this.settings}
|
settings={this.settings}
|
||||||
updateNode={(id, forceUpdate) => {
|
updateNode={(id, forceUpdate) => {
|
||||||
if (forceUpdate) {
|
if (forceUpdate) {
|
||||||
const data: any = this.graph.findDataById(id);
|
const data: GraphNodeModel = this.graph.findDataById(
|
||||||
|
id
|
||||||
|
) as GraphNodeModel;
|
||||||
data.conf = this.settings.getNodeConf(data.name);
|
data.conf = this.settings.getNodeConf(data.name);
|
||||||
data.size = Utils.calcTreeNodeSize(data);
|
data.size = Utils.calcTreeNodeSize(data);
|
||||||
this.changeWithoutAnim();
|
this.changeWithoutAnim();
|
||||||
|
this.save();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
const item = this.graph.findById(id);
|
const item = this.graph.findById(id);
|
||||||
item.draw();
|
item.draw();
|
||||||
@@ -533,12 +596,8 @@ export default class Editor extends React.Component<EditorProps, EditorState> {
|
|||||||
) : (
|
) : (
|
||||||
<TreePanel
|
<TreePanel
|
||||||
model={this.treeModel}
|
model={this.treeModel}
|
||||||
onRenameTree={(name: string) => {
|
onRenameTree={(name: string) => {}}
|
||||||
|
onRemoveTree={() => {}}
|
||||||
}}
|
|
||||||
onRemoveTree={() => {
|
|
||||||
|
|
||||||
}}
|
|
||||||
onChangeTreeDesc={(desc) => {
|
onChangeTreeDesc={(desc) => {
|
||||||
this.changeTreeDesc(desc);
|
this.changeTreeDesc(desc);
|
||||||
}}
|
}}
|
||||||
|
|||||||
+111
-51
@@ -1,6 +1,7 @@
|
|||||||
import G6 from "@antv/g6";
|
import G6 from "@antv/g6";
|
||||||
import { toBreakWord } from "../common/Utils";
|
import { toBreakWord } from "../common/Utils";
|
||||||
import Settings from "../main-process/Settings";
|
import Settings from "../main-process/Settings";
|
||||||
|
import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel";
|
||||||
|
|
||||||
const NODE_COLORS: any = {
|
const NODE_COLORS: any = {
|
||||||
["Composite"]: "rgb(91,237,32)",
|
["Composite"]: "rgb(91,237,32)",
|
||||||
@@ -8,8 +9,11 @@ const NODE_COLORS: any = {
|
|||||||
["Condition"]: "rgb(228,20,139)",
|
["Condition"]: "rgb(228,20,139)",
|
||||||
["Action"]: "rgb(91,143,249)",
|
["Action"]: "rgb(91,143,249)",
|
||||||
["Other"]: "rgb(112,112,112)",
|
["Other"]: "rgb(112,112,112)",
|
||||||
|
["Error"]: "rgb(255,0,0)",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SELECTED_LINE_WIDTH = 4;
|
||||||
|
|
||||||
export default function (settings: Settings) {
|
export default function (settings: Settings) {
|
||||||
const collapseStyle: any = {
|
const collapseStyle: any = {
|
||||||
"collapse-icon": {
|
"collapse-icon": {
|
||||||
@@ -22,6 +26,10 @@ export default function (settings: Settings) {
|
|||||||
{
|
{
|
||||||
options: {
|
options: {
|
||||||
type: "rect",
|
type: "rect",
|
||||||
|
anchorPoints: [
|
||||||
|
[0, 0.5],
|
||||||
|
[1, 0.5],
|
||||||
|
],
|
||||||
labelCfg: {
|
labelCfg: {
|
||||||
style: {
|
style: {
|
||||||
fill: "blue",
|
fill: "blue",
|
||||||
@@ -35,7 +43,10 @@ export default function (settings: Settings) {
|
|||||||
selected: {
|
selected: {
|
||||||
"main-box": {
|
"main-box": {
|
||||||
stroke: "yellow",
|
stroke: "yellow",
|
||||||
lineWidth: 5,
|
lineWidth: SELECTED_LINE_WIDTH,
|
||||||
|
},
|
||||||
|
"name-bg-selected": {
|
||||||
|
fill: "yellow",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
hover: {
|
hover: {
|
||||||
@@ -47,35 +58,39 @@ export default function (settings: Settings) {
|
|||||||
},
|
},
|
||||||
dragRight: {
|
dragRight: {
|
||||||
"drag-right": {
|
"drag-right": {
|
||||||
fillOpacity: 0.6,
|
fillOpacity: 0.7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dragUp: {
|
dragUp: {
|
||||||
"drag-up": {
|
"drag-up": {
|
||||||
fillOpacity: 0.6,
|
fillOpacity: 0.7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
dragDown: {
|
dragDown: {
|
||||||
"drag-down": {
|
"drag-down": {
|
||||||
fillOpacity: 0.6,
|
fillOpacity: 0.7,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
draw(cfg, group) {
|
draw(cfg, group) {
|
||||||
const nodeConf = settings.getNodeConf(cfg.name as string);
|
const nodeConf = settings.getNodeConf(cfg.name as string);
|
||||||
const classify = nodeConf.type || "Other";
|
let classify = nodeConf.type || "Other";
|
||||||
|
if (
|
||||||
|
!settings.hasNodeConf(cfg.name as string) ||
|
||||||
|
(cfg.path && (!cfg.children || (cfg.children as []).length == 0))
|
||||||
|
) {
|
||||||
|
classify = "Error";
|
||||||
|
}
|
||||||
const color = NODE_COLORS[classify] || "Other";
|
const color = NODE_COLORS[classify] || "Other";
|
||||||
var size = cfg.size ? (cfg.size as number[]) : [150, 40];
|
var size = cfg.size ? (cfg.size as number[]) : [150, 40];
|
||||||
const w = size[0];
|
const w = size[0];
|
||||||
const h = size[1];
|
const h = size[1];
|
||||||
const x0 = -w / 2;
|
const r = 4;
|
||||||
const y0 = -h / 2;
|
|
||||||
const r = 2;
|
|
||||||
const shape = group.addShape("rect", {
|
const shape = group.addShape("rect", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: x0,
|
x: 0,
|
||||||
y: y0,
|
y: 0,
|
||||||
width: w,
|
width: w,
|
||||||
height: h,
|
height: h,
|
||||||
stroke: color,
|
stroke: color,
|
||||||
@@ -87,15 +102,45 @@ export default function (settings: Settings) {
|
|||||||
draggable: true,
|
draggable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// is subtree
|
||||||
|
if (cfg.path && cfg.id !== "1") {
|
||||||
|
group.addShape("rect", {
|
||||||
|
attrs: {
|
||||||
|
x: -10,
|
||||||
|
y: -10,
|
||||||
|
width: w + 20,
|
||||||
|
height: h + 20,
|
||||||
|
stroke: "white",
|
||||||
|
lineWidth: 2.5,
|
||||||
|
lineDash: [5, 5],
|
||||||
|
radius: [r, r, r, r],
|
||||||
|
},
|
||||||
|
name: "subtree",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// name bg
|
// name bg
|
||||||
group.addShape("rect", {
|
group.addShape("rect", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: x0 + 1.5,
|
x: 0,
|
||||||
y: y0 + 1.5,
|
y: 0,
|
||||||
width: w - 3,
|
width: w,
|
||||||
height: 16,
|
height: 20,
|
||||||
fill: color,
|
fill: color,
|
||||||
// radius: r,
|
radius: [r, r, 0, 0],
|
||||||
|
},
|
||||||
|
name: "name-bg-selected",
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
group.addShape("rect", {
|
||||||
|
attrs: {
|
||||||
|
x: SELECTED_LINE_WIDTH / 2,
|
||||||
|
y: SELECTED_LINE_WIDTH / 2,
|
||||||
|
width: w - 5,
|
||||||
|
height: 20 - SELECTED_LINE_WIDTH / 2,
|
||||||
|
fill: color,
|
||||||
|
radius: [r, r, 0, 0],
|
||||||
},
|
},
|
||||||
name: "name-bg",
|
name: "name-bg",
|
||||||
draggable: true,
|
draggable: true,
|
||||||
@@ -105,8 +150,8 @@ export default function (settings: Settings) {
|
|||||||
group.addShape("text", {
|
group.addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
x: x0 - 3,
|
x: -3,
|
||||||
y: -8,
|
y: h / 2 - 8,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
text: cfg.id,
|
text: cfg.id,
|
||||||
@@ -122,8 +167,8 @@ export default function (settings: Settings) {
|
|||||||
var img = `./static/icons/${classify}.svg`;
|
var img = `./static/icons/${classify}.svg`;
|
||||||
group.addShape("image", {
|
group.addShape("image", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: x0 + 3,
|
x: 5,
|
||||||
y: y0 + 2,
|
y: 2,
|
||||||
height: 14,
|
height: 14,
|
||||||
width: 14,
|
width: 14,
|
||||||
img,
|
img,
|
||||||
@@ -135,8 +180,8 @@ export default function (settings: Settings) {
|
|||||||
if (cfg.debug) {
|
if (cfg.debug) {
|
||||||
group.addShape("image", {
|
group.addShape("image", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: x0 + 182,
|
x: 182,
|
||||||
y: y0 + 2,
|
y: 2,
|
||||||
height: 14,
|
height: 14,
|
||||||
width: 14,
|
width: 14,
|
||||||
img: `./static/icons/Debug.svg`,
|
img: `./static/icons/Debug.svg`,
|
||||||
@@ -149,8 +194,8 @@ export default function (settings: Settings) {
|
|||||||
group.addShape("text", {
|
group.addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
x: x0 + 18,
|
x: 22,
|
||||||
y: y0 + 4,
|
y: 3,
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
text: cfg.name,
|
text: cfg.name,
|
||||||
@@ -159,8 +204,8 @@ export default function (settings: Settings) {
|
|||||||
name: "name-text",
|
name: "name-text",
|
||||||
});
|
});
|
||||||
|
|
||||||
var x = x0 + 2;
|
var x = 5;
|
||||||
var y = y0 + 23;
|
var y = 24;
|
||||||
// desc text
|
// desc text
|
||||||
const desc = cfg.desc || nodeConf.desc;
|
const desc = cfg.desc || nodeConf.desc;
|
||||||
if (desc) {
|
if (desc) {
|
||||||
@@ -171,7 +216,7 @@ export default function (settings: Settings) {
|
|||||||
y,
|
y,
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
fontWeight: 800,
|
fontWeight: 800,
|
||||||
text: `备注:${desc}`,
|
text: `备注:${desc}`,
|
||||||
fill: "black",
|
fill: "black",
|
||||||
},
|
},
|
||||||
name: "desc-text",
|
name: "desc-text",
|
||||||
@@ -180,7 +225,7 @@ export default function (settings: Settings) {
|
|||||||
|
|
||||||
const args: any = cfg.args;
|
const args: any = cfg.args;
|
||||||
if (nodeConf.args && args && Object.keys(args).length > 0) {
|
if (nodeConf.args && args && Object.keys(args).length > 0) {
|
||||||
const { str, line } = toBreakWord(`参数:${JSON.stringify(args)}`, 35);
|
const { str, line } = toBreakWord(`参数:${JSON.stringify(args)}`, 34);
|
||||||
group.addShape("text", {
|
group.addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
@@ -198,7 +243,7 @@ export default function (settings: Settings) {
|
|||||||
|
|
||||||
const input: [] = cfg.input ? (cfg.input as []) : [];
|
const input: [] = cfg.input ? (cfg.input as []) : [];
|
||||||
if (nodeConf.input && input.length > 0) {
|
if (nodeConf.input && input.length > 0) {
|
||||||
const { str, line } = toBreakWord(`输入:${JSON.stringify(input)}`, 35);
|
const { str, line } = toBreakWord(`输入:${JSON.stringify(input)}`, 35);
|
||||||
group.addShape("text", {
|
group.addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
@@ -215,7 +260,7 @@ export default function (settings: Settings) {
|
|||||||
|
|
||||||
const output: [] = cfg.output ? (cfg.output as []) : [];
|
const output: [] = cfg.output ? (cfg.output as []) : [];
|
||||||
if (nodeConf.output && output.length > 0) {
|
if (nodeConf.output && output.length > 0) {
|
||||||
const { str, line } = toBreakWord(`输出:${JSON.stringify(output)}`, 35);
|
const { str, line } = toBreakWord(`输出:${JSON.stringify(output)}`, 35);
|
||||||
group.addShape("text", {
|
group.addShape("text", {
|
||||||
attrs: {
|
attrs: {
|
||||||
textBaseline: "top",
|
textBaseline: "top",
|
||||||
@@ -230,11 +275,26 @@ export default function (settings: Settings) {
|
|||||||
y += 20 * line;
|
y += 20 * line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cfg.path) {
|
||||||
|
group.addShape("text", {
|
||||||
|
attrs: {
|
||||||
|
textBaseline: "top",
|
||||||
|
x,
|
||||||
|
y: y + 20,
|
||||||
|
lineHeight: 20,
|
||||||
|
text: `子树:${cfg.path}`,
|
||||||
|
fill: "black",
|
||||||
|
},
|
||||||
|
name: "subtree-text",
|
||||||
|
});
|
||||||
|
y += 20;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(cfg.children) && cfg.children.length > 0) {
|
if (Array.isArray(cfg.children) && cfg.children.length > 0) {
|
||||||
group.addShape("marker", {
|
group.addShape("marker", {
|
||||||
attrs: {
|
attrs: {
|
||||||
x: w / 2,
|
x: w,
|
||||||
y: 0,
|
y: h / 2,
|
||||||
r: 6,
|
r: 6,
|
||||||
symbol: G6.Marker.collapse,
|
symbol: G6.Marker.collapse,
|
||||||
stroke: "#666",
|
stroke: "#666",
|
||||||
@@ -248,43 +308,43 @@ export default function (settings: Settings) {
|
|||||||
group.addShape("rect", {
|
group.addShape("rect", {
|
||||||
name: "drag-up",
|
name: "drag-up",
|
||||||
attrs: {
|
attrs: {
|
||||||
x: -w / 2,
|
x: 0,
|
||||||
y: -h / 2,
|
|
||||||
width: w,
|
|
||||||
height: h / 2,
|
|
||||||
fill: "blue",
|
|
||||||
fillOpacity: 0,
|
|
||||||
},
|
|
||||||
draggable: true,
|
|
||||||
// visible: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
group.addShape("rect", {
|
|
||||||
name: "drag-down",
|
|
||||||
attrs: {
|
|
||||||
x: -w / 2,
|
|
||||||
y: 0,
|
y: 0,
|
||||||
width: w,
|
width: w,
|
||||||
height: h / 2,
|
height: h / 2,
|
||||||
fill: "blue",
|
fill: "blue",
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
|
radius: [r, r, 0, 0],
|
||||||
|
},
|
||||||
|
draggable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
group.addShape("rect", {
|
||||||
|
name: "drag-down",
|
||||||
|
attrs: {
|
||||||
|
x: 0,
|
||||||
|
y: h / 2,
|
||||||
|
width: w,
|
||||||
|
height: h / 2,
|
||||||
|
fill: "blue",
|
||||||
|
fillOpacity: 0,
|
||||||
|
radius: [0, 0, r, r],
|
||||||
},
|
},
|
||||||
draggable: true,
|
draggable: true,
|
||||||
// visible: false,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
group.addShape("rect", {
|
group.addShape("rect", {
|
||||||
name: "drag-right",
|
name: "drag-right",
|
||||||
attrs: {
|
attrs: {
|
||||||
x: w * 0.1,
|
x: w / 2,
|
||||||
y: -h / 2,
|
y: 0,
|
||||||
width: w * 0.4,
|
width: w / 2,
|
||||||
height: h,
|
height: h,
|
||||||
fill: "blue",
|
fill: "blue",
|
||||||
fillOpacity: 0,
|
fillOpacity: 0,
|
||||||
|
radius: [0, r, r, 0],
|
||||||
},
|
},
|
||||||
draggable: true,
|
draggable: true,
|
||||||
// visible: false,
|
|
||||||
});
|
});
|
||||||
return shape;
|
return shape;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import Editor from "./Editor";
|
import { Tabs, message } from "antd";
|
||||||
import React, { Component } from "react";
|
|
||||||
import { Layout, Tabs, message } from "antd";
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
import * as Utils from "../common/Utils";
|
||||||
|
import React, { Component } from "react";
|
||||||
|
import Editor from "./Editor";
|
||||||
|
|
||||||
import "antd/dist/antd.dark.css";
|
import "antd/dist/antd.dark.css";
|
||||||
import MainEventType from "../common/MainEventType";
|
|
||||||
import { BehaviorTreeModel } from "../common/BehaviorTreeModel";
|
import { BehaviorTreeModel } from "../common/BehaviorTreeModel";
|
||||||
|
import MainEventType from "../common/MainEventType";
|
||||||
|
|
||||||
const { TabPane } = Tabs;
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
@@ -70,6 +72,31 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
message.success("已保存所有行为树");
|
message.success("已保存所有行为树");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcRenderer.on(MainEventType.BUILD, () => {
|
||||||
|
for (let k in this.editors) {
|
||||||
|
let editor = this.editors[k];
|
||||||
|
editor.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = Utils.getRemoteSettings();
|
||||||
|
const workdir = settings.workdir;
|
||||||
|
if (!fs.existsSync(workdir)) {
|
||||||
|
console.log("workdir not found:", workdir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const files = fs.readdirSync(workdir).filter((f) => {
|
||||||
|
return f.endsWith(".json");
|
||||||
|
});
|
||||||
|
for (const path of files) {
|
||||||
|
const builddir = fs.realpathSync(workdir + "/../build", "utf-8");
|
||||||
|
fs.mkdirSync(builddir, { recursive: true });
|
||||||
|
const buildpath = builddir + "/" + path;
|
||||||
|
console.log("build:", buildpath);
|
||||||
|
const treeModel = Utils.createBuildData(path, settings);
|
||||||
|
fs.writeFileSync(buildpath, JSON.stringify(treeModel, null, 2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcRenderer.on(MainEventType.UNDO, () => {
|
ipcRenderer.on(MainEventType.UNDO, () => {
|
||||||
const editor = this.getCurEditor();
|
const editor = this.getCurEditor();
|
||||||
editor?.undo();
|
editor?.undo();
|
||||||
@@ -88,7 +115,7 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getOpenTreesModel() {
|
getOpenTreesModel() {
|
||||||
const trees: BehaviorTreeModel[] = []
|
const trees: BehaviorTreeModel[] = [];
|
||||||
for (let k in this.editors) {
|
for (let k in this.editors) {
|
||||||
let editor = this.editors[k];
|
let editor = this.editors[k];
|
||||||
if (editor) {
|
if (editor) {
|
||||||
@@ -108,7 +135,7 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
openFile(path: string) {
|
openFile(path: string) {
|
||||||
if (!this.state.trees.find(e => e.filepath == path)) {
|
if (!this.state.trees.find((e) => e.filepath == path)) {
|
||||||
const trees = this.state.trees;
|
const trees = this.state.trees;
|
||||||
trees.push({ filepath: path });
|
trees.push({ filepath: path });
|
||||||
this.setState({ trees, curTree: path });
|
this.setState({ trees, curTree: path });
|
||||||
@@ -119,7 +146,7 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
|
|
||||||
closeFile(path: string) {
|
closeFile(path: string) {
|
||||||
let trees = this.state.trees;
|
let trees = this.state.trees;
|
||||||
trees = trees.filter(tree => tree.filepath != path);
|
trees = trees.filter((tree) => tree.filepath != path);
|
||||||
const length = trees.length;
|
const length = trees.length;
|
||||||
this.setState({ trees: trees, curTree: length > 0 ? trees[0].filepath : null });
|
this.setState({ trees: trees, curTree: length > 0 ? trees[0].filepath : null });
|
||||||
}
|
}
|
||||||
@@ -139,6 +166,8 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
defaultActiveKey={curTree}
|
defaultActiveKey={curTree}
|
||||||
activeKey={curTree}
|
activeKey={curTree}
|
||||||
onChange={(activeKey) => {
|
onChange={(activeKey) => {
|
||||||
|
const editor = this.editors[activeKey];
|
||||||
|
editor?.reload();
|
||||||
this.setState({ curTree: activeKey });
|
this.setState({ curTree: activeKey });
|
||||||
}}
|
}}
|
||||||
onEdit={(targetKey, action) => {
|
onEdit={(targetKey, action) => {
|
||||||
@@ -149,11 +178,14 @@ export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
|
|||||||
>
|
>
|
||||||
{trees.map((tree) => {
|
{trees.map((tree) => {
|
||||||
return (
|
return (
|
||||||
<TabPane tab={`${path.basename(tree.filepath)}${tree.unsave?"*":''}`} key={tree.filepath}>
|
<TabPane
|
||||||
|
tab={`${path.basename(tree.filepath)}${tree.unsave ? "*" : ""}`}
|
||||||
|
key={tree.filepath}
|
||||||
|
>
|
||||||
<Editor
|
<Editor
|
||||||
filepath={tree.filepath}
|
filepath={tree.filepath}
|
||||||
onChangeSaveState={(unsave) => {
|
onChangeSaveState={(unsave) => {
|
||||||
if(tree.unsave != unsave) {
|
if (tree.unsave != unsave) {
|
||||||
tree.unsave = unsave;
|
tree.unsave = unsave;
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,55 @@
|
|||||||
{"name":"hero","root":{"id":1,"name":"Selector","desc":"英雄测试AI","args":{},"children":[{"id":2,"name":"Sequence","desc":"攻击","args":{},"children":[{"id":3,"name":"FindEnemy","args":{"x":0,"y":0,"w":100,"h":50},"output":["enemy"]},{"id":4,"name":"Attack","args":{},"input":["enemy"]},{"id":5,"name":"Wait","args":{"time":10}}]},{"id":6,"name":"Sequence","desc":"移动","args":{},"children":[{"id":7,"name":"FindEnemy","args":{"w":1000,"h":500,"x":0,"y":0},"output":["enemy"]},{"id":8,"name":"MoveToTarget","args":{},"input":["enemy"]}]},{"id":9,"name":"Sequence","desc":"逃跑","args":{},"children":[{"id":10,"name":"GetHp","args":{},"output":["hp"]},{"id":11,"name":"Cmp","args":{"lt":50},"input":["hp"]},{"id":12,"name":"MoveToPos","args":{"x":0,"y":0}}]},{"id":13,"name":"Idle"}]},"desc":"英雄测试AI"}
|
{
|
||||||
|
"name": "hero",
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Selector",
|
||||||
|
"desc": "英雄测试AI",
|
||||||
|
"args": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "攻击",
|
||||||
|
"args": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "FindEnemy",
|
||||||
|
"args": { "x": 0, "y": 0, "w": 100, "h": 50 },
|
||||||
|
"output": ["enemy"]
|
||||||
|
},
|
||||||
|
{ "id": 4, "name": "Attack", "args": {}, "input": ["enemy"] },
|
||||||
|
{ "id": 5, "name": "Wait", "args": { "time": 10 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "移动",
|
||||||
|
"args": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "FindEnemy",
|
||||||
|
"args": { "w": 1000, "h": 500, "x": 0, "y": 0 },
|
||||||
|
"output": ["enemy"]
|
||||||
|
},
|
||||||
|
{ "id": 8, "name": "MoveToTarget", "args": {}, "input": ["enemy"] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "逃跑",
|
||||||
|
"args": {},
|
||||||
|
"children": [
|
||||||
|
{ "id": 10, "name": "GetHp", "args": {}, "output": ["hp"] },
|
||||||
|
{ "id": 11, "name": "Cmp", "args": { "lt": 50 }, "input": ["hp"] },
|
||||||
|
{ "id": 12, "name": "MoveToPos", "args": { "x": 0, "y": 0 } }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": 13, "name": "Idle" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"desc": "英雄测试AI"
|
||||||
|
}
|
||||||
@@ -18,10 +18,18 @@
|
|||||||
"args": {},
|
"args": {},
|
||||||
"output": [
|
"output": [
|
||||||
"hp"
|
"hp"
|
||||||
|
],
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "新建行为树",
|
||||||
|
"path": "subtree2.json"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 6,
|
||||||
"name": "Cmp",
|
"name": "Cmp",
|
||||||
"args": {
|
"args": {
|
||||||
"gt": 50,
|
"gt": 50,
|
||||||
@@ -32,17 +40,24 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 7,
|
||||||
"name": "Log",
|
"name": "Log",
|
||||||
"desc": "攻击",
|
"desc": "攻击",
|
||||||
"args": {
|
"args": {
|
||||||
"str": "Attack!"
|
"str": "Attack!"
|
||||||
}
|
},
|
||||||
|
"children": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "新建行为树",
|
||||||
|
"path": "subtree1.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 11,
|
||||||
"name": "Log",
|
"name": "Log",
|
||||||
"desc": "逃跑",
|
"desc": "逃跑",
|
||||||
"args": {
|
"args": {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "subtree",
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "新建行为树",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "Log",
|
||||||
|
"args": {
|
||||||
|
"str": "hello"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "Wait",
|
||||||
|
"args": {
|
||||||
|
"time": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"desc": "子树1"
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "subtree2",
|
||||||
|
"root": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Sequence",
|
||||||
|
"desc": "新建行为树",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "unknow"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"desc": "子树2"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<svg height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<title />
|
||||||
|
<path
|
||||||
|
d="M85.57,446.25H426.43a32,32,0,0,0,28.17-47.17L284.18,82.58c-12.09-22.44-44.27-22.44-56.36,0L57.4,399.08A32,32,0,0,0,85.57,446.25Z"
|
||||||
|
style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px" />
|
||||||
|
<path d="M250.26,195.39l5.74,122,5.73-121.95a5.74,5.74,0,0,0-5.79-6h0A5.74,5.74,0,0,0,250.26,195.39Z"
|
||||||
|
style="fill:none;stroke:#000;stroke-linecap:round;stroke-linejoin:round;stroke-width:32px" />
|
||||||
|
<path d="M256,397.25a20,20,0,1,1,20-20A20,20,0,0,1,256,397.25Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 663 B |
+2
-2
@@ -25,11 +25,11 @@ module.exports = {
|
|||||||
{
|
{
|
||||||
test: /\.less$/,
|
test: /\.less$/,
|
||||||
include: /node_modules/,
|
include: /node_modules/,
|
||||||
loaders: ['style-loader','css-loader','less-loader']
|
use: ['style-loader',{ loader: 'css-loader' },{ loader: 'less-loader' }]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/,
|
test: /\.css$/,
|
||||||
loaders: ['style-loader', 'css-loader']
|
use: ['style-loader', { loader: 'css-loader' }]
|
||||||
},
|
},
|
||||||
{//https://github.com/ashtuchkin/iconv-lite/issues/205
|
{//https://github.com/ashtuchkin/iconv-lite/issues/205
|
||||||
test: /node_modules[\/\\](iconv-lite)[\/\\].+/,
|
test: /node_modules[\/\\](iconv-lite)[\/\\].+/,
|
||||||
|
|||||||
Reference in new issue
Block a user