style: format codes

This commit is contained in:
duanhongbo committed 2020-11-12 17:24:08 +08:00
1 parent c3ea18e307
commit 00e79d641e
14 files changed
+2152 -2186

No files matched your search

+44 -44
View File
@@ -1,44 +1,44 @@
import { TreeGraphData } from "@antv/g6/lib/types"; import { TreeGraphData } from "@antv/g6/lib/types";
export interface ArgsDefType { export interface ArgsDefType {
name: string, name: string;
type: string, type: string;
desc: string, desc: string;
} }
export interface BehaviorNodeTypeModel { export interface BehaviorNodeTypeModel {
name: string; name: string;
type?: string; type?: string;
desc?: string; desc?: string;
args?: ArgsDefType[]; args?: ArgsDefType[];
input?: string[]; input?: string[];
output?: string[]; output?: string[];
doc?: string; doc?: string;
} }
export interface BehaviorNodeModel { export interface BehaviorNodeModel {
id: number; id: number;
name: string; name: string;
desc?: string; desc?: string;
args?: {[key: string]: any}; args?: { [key: string]: any };
input?: string[]; input?: string[];
output?: string[]; output?: string[];
children?: BehaviorNodeModel[]; children?: BehaviorNodeModel[];
debug?: boolean; debug?: boolean;
} }
export interface BehaviorTreeModel { export interface BehaviorTreeModel {
name: string; name: string;
root: BehaviorNodeModel; root: BehaviorNodeModel;
} }
export interface GraphNodeModel extends TreeGraphData { export interface GraphNodeModel extends TreeGraphData {
name: string; name: string;
desc?: string; desc?: string;
args?: {[key: string]: any}; args?: { [key: string]: any };
input?: string[]; input?: string[];
output?: string[]; output?: string[];
children?: GraphNodeModel[]; children?: GraphNodeModel[];
conf: BehaviorNodeTypeModel; conf: BehaviorNodeTypeModel;
size?: number[]; size?: number[];
} }
+12 -12
View File
@@ -1,12 +1,12 @@
export default { export default {
OPEN_FILE: "OPEN_FILE", OPEN_FILE: "OPEN_FILE",
OPEN_DIR: "OPEN_DIR", OPEN_DIR: "OPEN_DIR",
OPEN_WORKSPACE: "OPEN_WORKSPACE", OPEN_WORKSPACE: "OPEN_WORKSPACE",
CREATE_TREE: 'CREATE_TREE', CREATE_TREE: "CREATE_TREE",
CREATE_NODE: 'CREATE_NODE', CREATE_NODE: "CREATE_NODE",
DELETE_NODE: 'DELETE_NODE', DELETE_NODE: "DELETE_NODE",
COPY_NODE: 'COPY_NODE', COPY_NODE: "COPY_NODE",
PASTE_NODE: 'PASTE_NODE', PASTE_NODE: "PASTE_NODE",
SAVE: 'SAVE', SAVE: "SAVE",
SAVE_ALL: 'SAVE_ALL', SAVE_ALL: "SAVE_ALL",
} };
+130 -131
View File
@@ -1,131 +1,130 @@
import { TreeGraphData } from "@antv/g6/lib/types" import { TreeGraphData } from "@antv/g6/lib/types";
import { BehaviorNodeModel, BehaviorTreeModel, GraphNodeModel } from "./BehaviorTreeModel"; import { BehaviorNodeModel, BehaviorTreeModel, GraphNodeModel } from "./BehaviorTreeModel";
import Settings from "../main-process/Settings"; import Settings from "../main-process/Settings";
import { remote } from "electron"; import { remote } from "electron";
import * as path from 'path'; import * as path from "path";
export const cloneNodeData = (nodeData: GraphNodeModel) => { export const cloneNodeData = (nodeData: GraphNodeModel) => {
const newData: GraphNodeModel = { const newData: GraphNodeModel = {
id: nodeData.id + "new", id: nodeData.id + "new",
name: nodeData.name, name: nodeData.name,
conf: nodeData.conf, conf: nodeData.conf,
} };
return newData; return newData;
} };
export const refreshNodeId = (nodeData: GraphNodeModel, id?: number) => { export const refreshNodeId = (nodeData: GraphNodeModel, id?: number) => {
if (!id) { if (!id) {
id = 1; id = 1;
} }
nodeData.id = (id++).toString(); nodeData.id = (id++).toString();
if (nodeData.children) { if (nodeData.children) {
nodeData.children.forEach(child => { nodeData.children.forEach((child) => {
id = refreshNodeId(child, id); id = refreshNodeId(child, id);
}) });
} }
return id; return id;
} };
export const calcTreeNodeSize = (treeNode: GraphNodeModel) => { export const calcTreeNodeSize = (treeNode: GraphNodeModel) => {
var height = 40; var height = 40;
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)) {
height += 25; height += 25;
} }
} };
updateHeight(treeNode.conf.args); updateHeight(treeNode.conf.args);
updateHeight(treeNode.conf.input); updateHeight(treeNode.conf.input);
updateHeight(treeNode.conf.output); updateHeight(treeNode.conf.output);
return [200, height]; return [200, height];
} };
export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings) => { export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings) => {
const treeData: GraphNodeModel = { const treeData: GraphNodeModel = {
id: bNode.id.toString(), id: bNode.id.toString(),
name: bNode.name, name: bNode.name,
desc: bNode.desc, desc: bNode.desc,
args: bNode.args, args: bNode.args,
input: bNode.input, input: bNode.input,
output: bNode.output, output: bNode.output,
conf: settings.getNodeConf(bNode.name), conf: settings.getNodeConf(bNode.name),
} };
treeData.size = calcTreeNodeSize(treeData); treeData.size = calcTreeNodeSize(treeData);
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));
}) });
} }
calcTreeNodeSize(treeData); calcTreeNodeSize(treeData);
return treeData; return treeData;
} };
export const createFileData = (gNode: GraphNodeModel) => { export const createFileData = (gNode: GraphNodeModel) => {
const nodeData: BehaviorNodeModel = { const nodeData: BehaviorNodeModel = {
id: Number(gNode.id), id: Number(gNode.id),
name: gNode.name, name: gNode.name,
desc: gNode.desc || undefined, desc: gNode.desc || undefined,
args: gNode.args || undefined, args: gNode.args || undefined,
input: gNode.input || undefined, input: gNode.input || undefined,
output: gNode.output || undefined, output: gNode.output || undefined,
} };
if (gNode.children) { if (gNode.children) {
nodeData.children = []; nodeData.children = [];
gNode.children.forEach(child => { gNode.children.forEach((child) => {
nodeData.children.push(createFileData(child)); nodeData.children.push(createFileData(child));
}) });
} }
return nodeData; return nodeData;
} };
export const findParent = (node: TreeGraphData, id: string): TreeGraphData | null => { export const findParent = (node: TreeGraphData, id: string): TreeGraphData | null => {
if (node.children) { if (node.children) {
for (let child of node.children) { for (let child of node.children) {
if (child.id == id) { if (child.id == id) {
return node; return node;
} else { } else {
let parent = findParent(child, id); let parent = findParent(child, id);
if (parent) { if (parent) {
return parent; return parent;
} }
} }
}
} }
} return null;
return null; };
}
export const findFromAllChildren = (node: TreeGraphData, id: string): TreeGraphData | null => {
export const findFromAllChildren = (node: TreeGraphData, id: string): TreeGraphData | null => { if (node.id == id) {
if (node.id == id) { return node;
return node; }
} if (node.children) {
if (node.children) { for (let child of node.children) {
for (let child of node.children) { let found = findFromAllChildren(child, id);
let found = findFromAllChildren(child, id); if (found) {
if (found) { return found;
return found; }
} }
} }
} return null;
return null; };
}
export const getRemoteSettings = () => {
export const getRemoteSettings = () => { return remote.getGlobal("settings") as Settings;
return remote.getGlobal("settings") as Settings; };
}
export const fileName2treeName = (filename: string) => {
export const fileName2treeName = (filename: string) => { return path.basename(filename).slice(0, -5);
return path.basename(filename).slice(0, -5); };
}
export const createNewTree = (filename: string) => {
export const createNewTree = (filename: string) => { const tree: BehaviorTreeModel = {
const tree: BehaviorTreeModel = { name: fileName2treeName(filename),
name: fileName2treeName(filename), root: {
root: { id: 1,
id: 1, name: "Sequence",
name: 'Sequence', desc: "新建行为树",
desc: '新建行为树' },
} };
} return tree;
return tree; };
}
+375 -375
View File
@@ -1,375 +1,375 @@
import { import {
Menu, Menu,
app, app,
dialog, dialog,
BrowserWindow, BrowserWindow,
MenuItem, MenuItem,
WebContents, WebContents,
MenuItemConstructorOptions, MenuItemConstructorOptions,
} from "electron"; } from "electron";
import * as fs from "fs"; import * as fs from "fs";
import * as Utils from "../common/Utils"; import * as Utils from "../common/Utils";
import MainEventType from "../common/MainEventType"; import MainEventType from "../common/MainEventType";
import { MainProcess } from "./MainProcess"; import { MainProcess } from "./MainProcess";
import Settings from "./Settings"; import Settings from "./Settings";
export default class AppMenu { export default class AppMenu {
private mainProcess: MainProcess; private mainProcess: MainProcess;
private mainWindow: BrowserWindow; private mainWindow: BrowserWindow;
private webContents: WebContents; private webContents: WebContents;
private settings: Settings; private settings: Settings;
constructor(mainProcess: MainProcess) { constructor(mainProcess: MainProcess) {
this.mainProcess = mainProcess; this.mainProcess = mainProcess;
this.mainWindow = mainProcess.mainWindow; this.mainWindow = mainProcess.mainWindow;
this.webContents = mainProcess.mainWindow.webContents; this.webContents = mainProcess.mainWindow.webContents;
this.settings = mainProcess.settings; this.settings = mainProcess.settings;
} }
createMenu() { createMenu() {
const menu: Menu = new Menu(); const menu: Menu = new Menu();
menu.append(this.createWorkspaceMenu()); menu.append(this.createWorkspaceMenu());
menu.append(this.createFileMenu()); menu.append(this.createFileMenu());
menu.append(this.createNodeMenu()); menu.append(this.createNodeMenu());
menu.append(this.createToolsMenu()); menu.append(this.createToolsMenu());
return menu; return menu;
} }
private createFileMenu() { private createFileMenu() {
const fileItems: MenuItemConstructorOptions[] = []; const fileItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentFiles) { for (let path of this.settings.recentFiles) {
fileItems.push({ fileItems.push({
label: path, label: path,
click: () => { click: () => {
console.log("open recent file", path); console.log("open recent file", path);
}, },
}); });
} }
return new MenuItem({ return new MenuItem({
label: "行为树", label: "行为树",
submenu: [ submenu: [
{ {
label: "新建", label: "新建",
accelerator: "ctrl+n", accelerator: "ctrl+n",
click: () => { click: () => {
(async () => { (async () => {
const res = await dialog.showSaveDialog({ const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"], properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (!res.canceled) { if (!res.canceled) {
const path = res.filePath; const path = res.filePath;
fs.writeFileSync( fs.writeFileSync(
path, path,
JSON.stringify(Utils.createNewTree(path), null, 2) JSON.stringify(Utils.createNewTree(path), null, 2)
); );
this.webContents.send(MainEventType.CREATE_TREE, path); this.webContents.send(MainEventType.CREATE_TREE, path);
} }
})(); })();
}, },
}, },
{ {
label: "打开文件", label: "打开文件",
accelerator: "Ctrl+O", accelerator: "Ctrl+O",
click: () => { click: () => {
(async () => { (async () => {
const res = await dialog.showOpenDialog({ const res = await dialog.showOpenDialog({
properties: ["openFile"], properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (res.filePaths.length > 0) { if (res.filePaths.length > 0) {
const path = res.filePaths[0]; const path = res.filePaths[0];
if (this.settings.recentFiles.indexOf(path) < 0) { if (this.settings.recentFiles.indexOf(path) < 0) {
this.settings.recentFiles.unshift(path); this.settings.recentFiles.unshift(path);
this.settings.save(); this.settings.save();
this.mainProcess.rebuildMenu(); this.mainProcess.rebuildMenu();
} }
this.webContents.send(MainEventType.OPEN_FILE, path); this.webContents.send(MainEventType.OPEN_FILE, path);
} }
})(); })();
}, },
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "最近打开", label: "最近打开",
submenu: fileItems, submenu: fileItems,
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "保存", label: "保存",
accelerator: "ctrl+s", accelerator: "ctrl+s",
click: () => { click: () => {
this.webContents.send(MainEventType.SAVE); this.webContents.send(MainEventType.SAVE);
}, },
}, },
{ {
label: "全部保存", label: "全部保存",
accelerator: "ctrl+shift+s", accelerator: "ctrl+shift+s",
click: () => { click: () => {
this.webContents.send(MainEventType.SAVE_ALL); this.webContents.send(MainEventType.SAVE_ALL);
}, },
}, },
{ {
label: "合并导出Json", label: "合并导出Json",
click: () => { click: () => {
(async () => { (async () => {
const res = await dialog.showSaveDialog({ const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"], properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (res.filePath) { if (res.filePath) {
const curWorkspace = this.settings.curWorkspace; const curWorkspace = this.settings.curWorkspace;
curWorkspace.writeAllTrees(res.filePath,(err)=>{ curWorkspace.writeAllTrees(res.filePath, (err) => {
const msg = err?err:"导出成功"; const msg = err ? err : "导出成功";
dialog.showMessageBox({ dialog.showMessageBox({
type:"info", type: "info",
buttons:["ok"], buttons: ["ok"],
message:msg message: msg,
}) });
}); });
} }
})(); })();
}, },
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "新建节点", label: "新建节点",
accelerator: "insert", accelerator: "insert",
click: () => { click: () => {
this.webContents.send(MainEventType.CREATE_NODE, "unknow"); this.webContents.send(MainEventType.CREATE_NODE, "unknow");
}, },
}, },
{ {
label: "删除节点", label: "删除节点",
accelerator: "delete", accelerator: "delete",
click: () => { click: () => {
this.webContents.send(MainEventType.DELETE_NODE); this.webContents.send(MainEventType.DELETE_NODE);
}, },
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "复制节点", label: "复制节点",
accelerator: "ctrl+c", accelerator: "ctrl+c",
role: "copy", role: "copy",
click: () => { click: () => {
this.webContents.send(MainEventType.COPY_NODE); this.webContents.send(MainEventType.COPY_NODE);
}, },
}, },
{ {
label: "粘贴节点", label: "粘贴节点",
accelerator: "ctrl+v", accelerator: "ctrl+v",
click: () => { click: () => {
this.webContents.send(MainEventType.PASTE_NODE); this.webContents.send(MainEventType.PASTE_NODE);
}, },
}, },
], ],
}); });
} }
private createWorkspaceMenu() { private createWorkspaceMenu() {
const openWorkspace = (path: string) => { const openWorkspace = (path: string) => {
const curWorkspace = this.settings.curWorkspace; const curWorkspace = this.settings.curWorkspace;
curWorkspace.setFilepath(path); curWorkspace.setFilepath(path);
curWorkspace.load(); curWorkspace.load();
this.settings.pushRecentWorkspace(path); this.settings.pushRecentWorkspace(path);
this.mainProcess.rebuildMenu(); this.mainProcess.rebuildMenu();
this.webContents.send( this.webContents.send(
MainEventType.OPEN_DIR, MainEventType.OPEN_DIR,
curWorkspace.getWorkdir(), curWorkspace.getWorkdir(),
curWorkspace.getFilepath() curWorkspace.getFilepath()
); );
}; };
const saveToNewPath = () => { const saveToNewPath = () => {
(async () => { (async () => {
const res = await dialog.showSaveDialog({ const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"], properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (!res.canceled) { if (!res.canceled) {
this.settings.curWorkspace.setFilepath(res.filePath); this.settings.curWorkspace.setFilepath(res.filePath);
this.settings.curWorkspace.save(); this.settings.curWorkspace.save();
openWorkspace(res.filePath); openWorkspace(res.filePath);
} }
})(); })();
}; };
const recentItems: MenuItemConstructorOptions[] = []; const recentItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentWorkspaces) { for (let path of this.settings.recentWorkspaces) {
recentItems.push({ recentItems.push({
label: path, label: path,
click: () => { click: () => {
console.log("open recent workspace", path); console.log("open recent workspace", path);
openWorkspace(path); openWorkspace(path);
}, },
}); });
} }
return new MenuItem({ return new MenuItem({
label: "工作区", label: "工作区",
submenu: [ submenu: [
{ {
label: "打开", label: "打开",
click: () => { click: () => {
(async () => { (async () => {
const res = await dialog.showOpenDialog({ const res = await dialog.showOpenDialog({
properties: ["openFile"], properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (res.filePaths.length > 0) { if (res.filePaths.length > 0) {
openWorkspace(res.filePaths[0]); openWorkspace(res.filePaths[0]);
} }
})(); })();
}, },
}, },
{ {
label: "保存", label: "保存",
click: () => { click: () => {
if (this.settings.curWorkspace.getFilepath()) { if (this.settings.curWorkspace.getFilepath()) {
this.settings.curWorkspace.save(); this.settings.curWorkspace.save();
} else { } else {
saveToNewPath(); saveToNewPath();
} }
}, },
}, },
{ {
label: "另存为", label: "另存为",
click: () => { click: () => {
saveToNewPath(); saveToNewPath();
}, },
}, },
{ {
label: "最近打开", label: "最近打开",
submenu: recentItems, submenu: recentItems,
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "打开目录", label: "打开目录",
accelerator: "Ctrl+Shift+O", accelerator: "Ctrl+Shift+O",
click: () => { click: () => {
if (!this.settings.curWorkspace.getNodeConfPath()) { if (!this.settings.curWorkspace.getNodeConfPath()) {
dialog.showErrorBox("警告", "请先指定节点定义配置!"); dialog.showErrorBox("警告", "请先指定节点定义配置!");
return; return;
} }
(async () => { (async () => {
const res = await dialog.showOpenDialog({ const res = await dialog.showOpenDialog({
properties: ["openDirectory"], properties: ["openDirectory"],
}); });
if (res.filePaths.length > 0) { if (res.filePaths.length > 0) {
const curWorkspace = this.settings.curWorkspace; const curWorkspace = this.settings.curWorkspace;
const path = res.filePaths[0]; const path = res.filePaths[0];
curWorkspace.setWorkdir(path); curWorkspace.setWorkdir(path);
curWorkspace.save(); curWorkspace.save();
this.webContents.send( this.webContents.send(
MainEventType.OPEN_DIR, MainEventType.OPEN_DIR,
path, path,
curWorkspace.getFilepath() curWorkspace.getFilepath()
); );
} }
})(); })();
}, },
}, },
{ {
label: "节点定义", label: "节点定义",
submenu: [ submenu: [
{ {
label: "选择文件", label: "选择文件",
click: () => { click: () => {
(async () => { (async () => {
const res = await dialog.showOpenDialog({ const res = await dialog.showOpenDialog({
properties: ["openFile"], properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }], filters: [{ name: "Json", extensions: ["json"] }],
}); });
if (res.filePaths.length > 0) { if (res.filePaths.length > 0) {
const nodeConfigPath = res.filePaths[0]; const nodeConfigPath = res.filePaths[0];
this.settings.curWorkspace.setNodeConfPath(nodeConfigPath); this.settings.curWorkspace.setNodeConfPath(nodeConfigPath);
this.mainProcess.rebuildMenu(); this.mainProcess.rebuildMenu();
} }
})(); })();
}, },
}, },
{ type: "separator" }, { type: "separator" },
{ label: this.settings.nodeConfPath }, { label: this.settings.nodeConfPath },
], ],
}, },
{ type: "separator" }, { type: "separator" },
{ {
label: "关闭", label: "关闭",
click: () => { click: () => {
app.quit(); app.quit();
}, },
}, },
], ],
}); });
} }
private createToolsMenu() { private createToolsMenu() {
return new MenuItem({ return new MenuItem({
label: "开发工具", label: "开发工具",
submenu: [ submenu: [
{ {
label: "打开控制台", label: "打开控制台",
accelerator: "Ctrl+Shift+I", accelerator: "Ctrl+Shift+I",
click: (_, browserWindow) => { click: (_, browserWindow) => {
browserWindow.webContents.toggleDevTools(); browserWindow.webContents.toggleDevTools();
}, },
}, },
{ {
label: "重载", label: "重载",
accelerator: "Ctrl+R", accelerator: "Ctrl+R",
click: (_, browserWindow) => { click: (_, browserWindow) => {
browserWindow.reload(); browserWindow.reload();
}, },
}, },
], ],
}); });
} }
private createNodeMenu() { private createNodeMenu() {
const classifyItems: MenuItemConstructorOptions[] = []; const classifyItems: MenuItemConstructorOptions[] = [];
const map: { [key: string]: MenuItemConstructorOptions } = {}; const map: { [key: string]: MenuItemConstructorOptions } = {};
for (let t of this.settings.nodeClassify) { for (let t of this.settings.nodeClassify) {
let item: MenuItemConstructorOptions = { let item: MenuItemConstructorOptions = {
id: t.classify, id: t.classify,
label: `${t.classify}(${t.desc})`, label: `${t.classify}(${t.desc})`,
submenu: [], submenu: [],
}; };
classifyItems.push(item); classifyItems.push(item);
map[t.classify] = item; map[t.classify] = item;
} }
const other: MenuItemConstructorOptions = { const other: MenuItemConstructorOptions = {
id: "other", id: "other",
label: "其它", label: "其它",
submenu: [], submenu: [],
}; };
var hasOther = false; var hasOther = false;
for (let node of this.settings.nodeConfig) { for (let node of this.settings.nodeConfig) {
const item: MenuItemConstructorOptions = { const item: MenuItemConstructorOptions = {
label: `${node.name}(${node.desc})`, label: `${node.name}(${node.desc})`,
click: () => { click: () => {
console.log("create node", node.name); console.log("create node", node.name);
this.webContents.send(MainEventType.CREATE_NODE, node.name); this.webContents.send(MainEventType.CREATE_NODE, node.name);
}, },
}; };
let typeItem = map[node.type]; let typeItem = map[node.type];
if (!typeItem) { if (!typeItem) {
typeItem = other; typeItem = other;
hasOther = true; hasOther = true;
} }
if (typeItem.submenu instanceof Menu) { if (typeItem.submenu instanceof Menu) {
console.log("typeItem.submenu error", typeItem); console.log("typeItem.submenu error", typeItem);
} else { } else {
typeItem.submenu.push(item); typeItem.submenu.push(item);
} }
} }
if (hasOther) { if (hasOther) {
classifyItems.push(other); classifyItems.push(other);
} }
return new MenuItem({ return new MenuItem({
label: "新建节点", label: "新建节点",
submenu: classifyItems, submenu: classifyItems,
}); });
} }
} }
+70 -72
View File
@@ -1,72 +1,70 @@
import { app, BrowserWindow, Menu, MenuItem, dialog, nativeTheme } from 'electron'; import { app, BrowserWindow, Menu, MenuItem, dialog, nativeTheme } from "electron";
import AppMenu from './AppMenu' import AppMenu from "./AppMenu";
import Settings from './Settings'; import Settings from "./Settings";
import MainEventType from '../common/MainEventType'; import MainEventType from "../common/MainEventType";
const electronLocalshortcut = require('electron-localshortcut'); const electronLocalshortcut = require("electron-localshortcut");
// 一些暴露给render-process的全局变量 // 一些暴露给render-process的全局变量
export interface Global { export interface Global {
settings: Settings settings: Settings;
} }
declare var global: Global; declare var global: Global;
export class MainProcess { export class MainProcess {
mainWindow: BrowserWindow; mainWindow: BrowserWindow;
appMenu: AppMenu; appMenu: AppMenu;
settings: Settings; settings: Settings;
constructor() { constructor() {
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, "Ctrl+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, "Ctrl+V", () => {
this.mainWindow.webContents.send(MainEventType.PASTE_NODE); this.mainWindow.webContents.send(MainEventType.PASTE_NODE);
}); });
}) });
app.on('window-all-closed', () => { app.on("window-all-closed", () => {
if (process.platform !== 'darwin') { if (process.platform !== "darwin") {
app.quit() app.quit();
} }
}) });
app.on('activate', () => { app.on("activate", () => {
if (this.mainWindow === null) { if (this.mainWindow === null) {
this.createWindow() this.createWindow();
} }
}); });
} }
createWindow() { createWindow() {
this.settings = new Settings(); this.settings = new Settings();
global.settings = this.settings; global.settings = this.settings;
this.mainWindow = new BrowserWindow({ this.mainWindow = new BrowserWindow({
width: 1280, width: 1280,
height: 800, height: 800,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
enableRemoteModule: true, enableRemoteModule: true,
} },
// fullscreenable:false, // fullscreenable:false,
// maximizable:false // maximizable:false
}); });
this.mainWindow.maximize(); this.mainWindow.maximize();
// mainWindow.webContents.openDevTools(); // mainWindow.webContents.openDevTools();
this.mainWindow.loadFile('index.html'); this.mainWindow.loadFile("index.html");
this.mainWindow.on('closed', function () { this.mainWindow.on("closed", function () {
this.mainWindow = null this.mainWindow = null;
}); });
this.appMenu = new AppMenu(this); this.appMenu = new AppMenu(this);
this.rebuildMenu(); this.rebuildMenu();
} }
rebuildMenu() { rebuildMenu() {
Menu.setApplicationMenu(this.appMenu.createMenu()); Menu.setApplicationMenu(this.appMenu.createMenu());
} }
} }
export default new MainProcess();
export default new MainProcess();
+89 -90
View File
@@ -1,90 +1,89 @@
import * as fs from 'fs'; import * as fs from "fs";
import { BehaviorNodeTypeModel } from '../common/BehaviorTreeModel'; import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel";
import Workspace from './Workspace'; import Workspace from "./Workspace";
export interface BehaviorNodeClassify { export interface BehaviorNodeClassify {
classify: string; classify: string;
desc?: string; desc?: string;
} }
export interface SettingsModel { export interface SettingsModel {
recentWorkspaces?: string[]; recentWorkspaces?: string[];
recentFiles?: string[]; recentFiles?: string[];
nodeClassify?: BehaviorNodeClassify[]; nodeClassify?: BehaviorNodeClassify[];
} }
const settingPath = 'settings.json'; const settingPath = "settings.json";
const sampleNodeClassify: BehaviorNodeClassify[] = [ const sampleNodeClassify: BehaviorNodeClassify[] = [
{ classify: "Composite", desc: "复合节点" }, { classify: "Composite", desc: "复合节点" },
{ classify: "Decorator", desc: "修饰节点" }, { classify: "Decorator", desc: "修饰节点" },
{ classify: "Condition", desc: "条件节点" }, { classify: "Condition", desc: "条件节点" },
{ classify: "Action", desc: "行为节点" }, { classify: "Action", desc: "行为节点" },
] ];
export default class Settings {
export default class Settings { private settings: SettingsModel;
private settings: SettingsModel; curWorkspace?: Workspace;
curWorkspace?: Workspace;
constructor() {
constructor() { this.load();
this.load(); }
}
get nodeConfPath() {
get nodeConfPath() { return this.curWorkspace.getNodeConfPath();
return this.curWorkspace.getNodeConfPath(); }
} get recentWorkspaces() {
get recentWorkspaces() { return this.settings.recentWorkspaces;
return this.settings.recentWorkspaces; }
} get recentFiles() {
get recentFiles() { return this.settings.recentFiles;
return this.settings.recentFiles; }
} get nodeConfig() {
get nodeConfig() { return this.curWorkspace.nodeConf;
return this.curWorkspace.nodeConf; }
} get nodeClassify() {
get nodeClassify() { return this.settings.nodeClassify;
return this.settings.nodeClassify; }
}
set(config: SettingsModel) {
set(config: SettingsModel) { this.settings = {
this.settings = { ...this.settings,
...this.settings, ...config,
...config };
}; this.save();
this.save(); }
}
load() {
load() { if (fs.existsSync(settingPath)) {
if (fs.existsSync(settingPath)) { const str = fs.readFileSync(settingPath, "utf8");
const str = fs.readFileSync(settingPath, 'utf8'); this.settings = JSON.parse(str);
this.settings = JSON.parse(str); } else {
} else { this.settings = {
this.settings = { recentWorkspaces: ["sample/workspace.json"],
recentWorkspaces: ['sample/workspace.json'], recentFiles: [],
recentFiles: [], nodeClassify: sampleNodeClassify,
nodeClassify: sampleNodeClassify, };
}; this.save();
this.save(); }
} this.curWorkspace = new Workspace(this.settings.recentWorkspaces[0]);
this.curWorkspace = new Workspace(this.settings.recentWorkspaces[0]); this.curWorkspace.load();
this.curWorkspace.load(); }
}
save() {
save() { fs.writeFileSync(settingPath, JSON.stringify(this.settings, null, 2));
fs.writeFileSync(settingPath, JSON.stringify(this.settings, null, 2)); }
}
getNodeConf(name: string) {
getNodeConf(name: string) { return this.curWorkspace.getNodeConf(name);
return this.curWorkspace.getNodeConf(name); }
}
pushRecentWorkspace(path: string) {
pushRecentWorkspace(path: string) { if (this.settings.recentWorkspaces.indexOf(path) < 0) {
if (this.settings.recentWorkspaces.indexOf(path) < 0) { this.settings.recentWorkspaces.unshift(path);
this.settings.recentWorkspaces.unshift(path); console.log("push", path);
console.log("push", path); } else {
} else { console.log("already in path", this.settings.recentWorkspaces, path);
console.log("already in path", this.settings.recentWorkspaces, path); }
} this.save();
this.save(); }
} }
}
+139 -137
View File
@@ -1,137 +1,139 @@
import * as fs from "fs"; import * as fs from "fs";
import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel"; import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel";
import * as glob from "glob"; import * as glob from "glob";
import * as path from "path"; import * as path from "path";
export interface WorkspaceModel { export interface WorkspaceModel {
isRelative?: boolean; isRelative?: boolean;
nodeConfPath: string; nodeConfPath: string;
workdir: string; workdir: string;
} }
const unknowNodeType: BehaviorNodeTypeModel = { const unknowNodeType: BehaviorNodeTypeModel = {
name: "unknow", name: "unknow",
desc: "新建节点", desc: "新建节点",
type: "Action", type: "Action",
}; };
export default class Workspace { export default class Workspace {
private filepath: string; private filepath: string;
private nodeConfPath: string; private nodeConfPath: string;
private workdir: string; private workdir: string;
private name2conf: { [name: string]: BehaviorNodeTypeModel } = {}; private name2conf: { [name: string]: BehaviorNodeTypeModel } = {};
private types: BehaviorNodeTypeModel[] = []; private types: BehaviorNodeTypeModel[] = [];
constructor(filepath: string) { constructor(filepath: string) {
this.filepath = filepath; this.filepath = filepath;
} }
load() { load() {
if (!this.filepath) { if (!this.filepath) {
return; return;
} }
try { try {
const str = fs.readFileSync(this.filepath, 'utf8'); const str = fs.readFileSync(this.filepath, "utf8");
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 = root + '/' + model.nodeConfPath; this.nodeConfPath = root + "/" + model.nodeConfPath;
this.workdir = root + '/' + model.workdir; this.workdir = root + "/" + model.workdir;
} else { } else {
this.nodeConfPath = model.nodeConfPath; this.nodeConfPath = model.nodeConfPath;
this.workdir = model.workdir; this.workdir = model.workdir;
} }
this.initNodeConf(); this.initNodeConf();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
private initNodeConf() { private initNodeConf() {
if (!this.nodeConfPath) { if (!this.nodeConfPath) {
return; return;
} }
const types: BehaviorNodeTypeModel[] = JSON.parse(fs.readFileSync(this.nodeConfPath, 'utf8')); const types: BehaviorNodeTypeModel[] = JSON.parse(
this.name2conf = {}; fs.readFileSync(this.nodeConfPath, "utf8")
types.forEach(t => { );
this.name2conf[t.name] = t; this.name2conf = {};
}) types.forEach((t) => {
this.types = types; this.name2conf[t.name] = t;
} });
this.types = types;
getNodeConfPath() { }
return this.nodeConfPath;
} getNodeConfPath() {
setNodeConfPath(path: string) { return this.nodeConfPath;
this.nodeConfPath = path; }
this.initNodeConf(); setNodeConfPath(path: string) {
} this.nodeConfPath = path;
getWorkdir(): string { this.initNodeConf();
return this.workdir; }
} getWorkdir(): string {
setWorkdir(workdir: string) { return this.workdir;
this.workdir = workdir; }
} setWorkdir(workdir: string) {
this.workdir = workdir;
writeAllTrees(outFilePath: string, cb?: (err: string) => void) { }
if (!fs.existsSync(this.getWorkdir())) {
cb && cb("请先打开工作区"); writeAllTrees(outFilePath: string, cb?: (err: string) => void) {
return; if (!fs.existsSync(this.getWorkdir())) {
} cb && cb("请先打开工作区");
glob.glob(path.join(this.getWorkdir(), "**/*.json"), function (err, files) { return;
if (err) { }
console.error(err); glob.glob(path.join(this.getWorkdir(), "**/*.json"), function (err, files) {
cb && cb(err.message); if (err) {
return; console.error(err);
} cb && cb(err.message);
return;
let result = new Array(); }
files.forEach((file) => {
const data = fs.readFileSync(file, "utf8"); let result = new Array();
result.push(JSON.parse(data)); files.forEach((file) => {
}); const data = fs.readFileSync(file, "utf8");
result.push(JSON.parse(data));
fs.writeFileSync(outFilePath, JSON.stringify(result, null, 2)); });
cb && cb(null);
}); fs.writeFileSync(outFilePath, JSON.stringify(result, null, 2));
} cb && cb(null);
});
save(filepath?: string) { }
if (filepath) {
this.filepath = filepath; save(filepath?: string) {
} if (filepath) {
if (!this.filepath) { this.filepath = filepath;
return; }
} if (!this.filepath) {
fs.writeFileSync( return;
this.filepath, }
JSON.stringify( fs.writeFileSync(
{ this.filepath,
nodeConfPath: this.nodeConfPath, JSON.stringify(
workdir: this.workdir, {
}, nodeConfPath: this.nodeConfPath,
null, workdir: this.workdir,
2 },
) null,
); 2
} )
);
get nodeConf() { }
return this.types;
} get nodeConf() {
return this.types;
getFilepath() { }
return this.filepath;
} getFilepath() {
return this.filepath;
setFilepath(filepath: string) { }
this.filepath = filepath;
} setFilepath(filepath: string) {
this.filepath = filepath;
getNodeConf(name: string) { }
return this.name2conf[name] || unknowNodeType;
} getNodeConf(name: string) {
} return this.name2conf[name] || unknowNodeType;
}
}
+267 -273
View File
@@ -1,297 +1,291 @@
import React from 'react'; import React from "react";
import { Card, Divider, Form, Input, AutoComplete, Select, Switch, InputNumber, notification, message } from 'antd'; import {
import { INode } from '@antv/g6/lib/interface/item'; Card,
import { BehaviorNodeModel, BehaviorNodeTypeModel, ArgsDefType } from '../../common/BehaviorTreeModel'; Divider,
import Settings from '../../main-process/Settings'; Form,
import { FormInstance } from 'antd/lib/form'; Input,
import Markdown from 'react-markdown'; AutoComplete,
Select,
Switch,
InputNumber,
notification,
message,
} from "antd";
import { INode } from "@antv/g6/lib/interface/item";
import {
BehaviorNodeModel,
BehaviorNodeTypeModel,
ArgsDefType,
} from "../../common/BehaviorTreeModel";
import Settings from "../../main-process/Settings";
import { FormInstance } from "antd/lib/form";
import Markdown from "react-markdown";
const { Item } = Form; const { Item } = Form;
interface NodePanelProps { interface NodePanelProps {
model: BehaviorNodeModel; model: BehaviorNodeModel;
settings: Settings; settings: Settings;
updateNode: (id: string, forceUpdate:boolean) => void; updateNode: (id: string, forceUpdate: boolean) => void;
} }
interface NodePanelState { interface NodePanelState {}
}
export default class NodePanel extends React.Component<NodePanelProps> { export default class NodePanel extends React.Component<NodePanelProps> {
formRef = React.createRef<FormInstance>(); formRef = React.createRef<FormInstance>();
componentDidUpdate() { componentDidUpdate() {
this.formRef.current.resetFields(); this.formRef.current.resetFields();
this.formRef.current.setFieldsValue(this.getInitialValues()); this.formRef.current.setFieldsValue(this.getInitialValues());
}
getInitialValues() {
const { model } = this.props;
const initialValues: any = {
name: model.name,
desc: model.desc,
debug: model.debug,
customArgs: model.args ? JSON.stringify(model.args, null, " ") : '',
};
if (model.args) {
for (let k in model.args) {
initialValues[`args.${k}`] = model.args[k];
}
}
if (model.input) {
model.input.forEach((v, i) => {
initialValues[`input.${i}`] = v;
})
}
if (model.output) {
model.output.forEach((v, i) => {
initialValues[`output.${i}`] = v;
})
}
return initialValues;
}
onFinish = (values: any) => {
console.log('Success:', values);
const { updateNode, model, settings } = this.props;
const conf = settings.getNodeConf(values.name)
if (!conf) {
notification.warn({ message: `节点${values.name}未定义` });
return;
}
var forceUpdate = false;
if (model.name != values.name) {
model.name = values.name;
forceUpdate = true;
}
model.desc = values.desc;
var args: any = {};
if (values.customArgs) {
try {
args = JSON.parse(values.customArgs);
} catch (e) {
message.warn(`您输入的自定义参数不符合json格式${values.customArgs}`);
return;
}
} }
if (conf.args) { getInitialValues() {
conf.args.forEach(e => { const { model } = this.props;
const k = 'args.' + e.name; const initialValues: any = {
if (e.type.indexOf('number') > 0) { name: model.name,
args[e.name] = Number(values[k]); desc: model.desc,
debug: model.debug,
customArgs: model.args ? JSON.stringify(model.args, null, " ") : "",
};
if (model.args) {
for (let k in model.args) {
initialValues[`args.${k}`] = model.args[k];
}
}
if (model.input) {
model.input.forEach((v, i) => {
initialValues[`input.${i}`] = v;
});
}
if (model.output) {
model.output.forEach((v, i) => {
initialValues[`output.${i}`] = v;
});
}
return initialValues;
}
onFinish = (values: any) => {
console.log("Success:", values);
const { updateNode, model, settings } = this.props;
const conf = settings.getNodeConf(values.name);
if (!conf) {
notification.warn({ message: `节点${values.name}未定义` });
return;
}
var forceUpdate = false;
if (model.name != values.name) {
model.name = values.name;
forceUpdate = true;
}
model.desc = values.desc;
var args: any = {};
if (values.customArgs) {
try {
args = JSON.parse(values.customArgs);
} catch (e) {
message.warn(`您输入的自定义参数不符合json格式${values.customArgs}`);
return;
}
}
if (conf.args) {
conf.args.forEach((e) => {
const k = "args." + e.name;
if (e.type.indexOf("number") > 0) {
args[e.name] = Number(values[k]);
} else {
args[e.name] = values[k];
}
});
}
model.args = args;
this.formRef.current.setFieldsValue({
customArgs: model.args ? JSON.stringify(model.args, null, " ") : "",
});
if (conf.input) {
model.input = [];
conf.input.forEach((e, i) => {
model.input.push(values["input." + i] || "");
});
} else { } else {
args[e.name] = values[k]; model.input = null;
} }
})
}
model.args = args; if (conf.output) {
this.formRef.current.setFieldsValue({ customArgs: model.args ? JSON.stringify(model.args, null, " ") : '' }); model.output = [];
conf.output.forEach((e, i) => {
model.output.push(values["output." + i] || "");
});
} else {
model.output = null;
}
if (conf.input) { if (forceUpdate) {
model.input = []; this.forceUpdate();
conf.input.forEach((e, i) => { }
model.input.push(values['input.' + i] || ''); updateNode(model.id.toString(), forceUpdate);
})
} else {
model.input = null;
}
if (conf.output) {
model.output = [];
conf.output.forEach((e, i) => {
model.output.push(values['output.' + i] || '');
})
} else {
model.output = null;
}
if (forceUpdate) {
this.forceUpdate();
}
updateNode(model.id.toString(), forceUpdate);
};
onFinishFailed = (errorInfo: any) => {
console.log('Failed:', errorInfo);
};
handleSubmit = () => {
console.log("handleSubmit");
this.formRef.current.submit();
}
render() {
const { model, settings } = this.props;
const conf = settings.getNodeConf(model.name);
const title = conf.desc;
const options: any = [];
settings.nodeConfig.map((e) => {
options.push({ label: `${e.name}(${e.desc})`, value: e.name });
})
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
}; };
return (
<Card title={title} style={{ height: window.screen.height - 100, overflow: 'auto' }}>
<Form
{...layout}
name="basic"
onFinish={this.onFinish}
initialValues={this.getInitialValues()}
ref={this.formRef}
>
<Item
label="节点id"
>
<Input value={model.id} disabled={true} />
</Item>
<Item
label="节点名称"
name="name"
>
<AutoComplete
options={options}
onBlur={this.handleSubmit}
filterOption={(inputValue: string, option: any) => {
return option.label.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1
}}
/>
</Item>
<Item
label="节点说明"
name="desc"
>
<Input onBlur={this.handleSubmit} />
</Item>
<Item
label="调试开关"
name="debug"
valuePropName='checked'
>
<Switch onChange={this.handleSubmit} />
</Item>
<Markdown source={conf.doc} />
{this.renderInputs(conf)}
{this.renderArgs(conf)}
{this.renderOutputs(conf)}
</Form>
</Card >
)
}
renderArgs(conf: BehaviorNodeTypeModel) { onFinishFailed = (errorInfo: any) => {
if (!conf || !conf.args || conf.args.length == 0) { console.log("Failed:", errorInfo);
return null; };
handleSubmit = () => {
console.log("handleSubmit");
this.formRef.current.submit();
};
render() {
const { model, settings } = this.props;
const conf = settings.getNodeConf(model.name);
const title = conf.desc;
const options: any = [];
settings.nodeConfig.map((e) => {
options.push({ label: `${e.name}(${e.desc})`, value: e.name });
});
const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 18 },
};
return (
<Card title={title} style={{ height: window.screen.height - 100, overflow: "auto" }}>
<Form
{...layout}
name="basic"
onFinish={this.onFinish}
initialValues={this.getInitialValues()}
ref={this.formRef}
>
<Item label="节点id">
<Input value={model.id} disabled={true} />
</Item>
<Item label="节点名称" name="name">
<AutoComplete
options={options}
onBlur={this.handleSubmit}
filterOption={(inputValue: string, option: any) => {
return (
option.label.toUpperCase().indexOf(inputValue.toUpperCase()) !==
-1
);
}}
/>
</Item>
<Item label="节点说明" name="desc">
<Input onBlur={this.handleSubmit} />
</Item>
<Item label="调试开关" name="debug" valuePropName="checked">
<Switch onChange={this.handleSubmit} />
</Item>
<Markdown source={conf.doc} />
{this.renderInputs(conf)}
{this.renderArgs(conf)}
{this.renderOutputs(conf)}
</Form>
</Card>
);
} }
// 普通参数 renderArgs(conf: BehaviorNodeTypeModel) {
const normalArgs = (e: ArgsDefType) => { if (!conf || !conf.args || conf.args.length == 0) {
const required = e.type.indexOf("?") == -1; return null;
if (e.type.indexOf("string") >= 0) {
return <Input onBlur={this.handleSubmit} />;
} else if (e.type.indexOf("int") >= 0) {
return <InputNumber style={{ width: '100%' }} onBlur={this.handleSubmit} />;
} else if (e.type.indexOf("boolean") >= 0) {
return <Switch onChange={this.handleSubmit} />;
} else if (e.type.indexOf("lua") >= 0) {
return <Input onBlur={this.handleSubmit} placeholder={'公式'} />;
}
}
// 自定义参数
const customArgs = () => {
return (
<Item
name='customArgs'
label='自定义'
key='customArgs'
>
<Input.TextArea onBlur={this.handleSubmit} style={{ minHeight: 100 }} />
</Item>
)
}
return (
<div>
<Divider orientation="left"><h3>常量参数</h3></Divider>
{conf && conf.args && conf.args.map((e, i: number) => {
const required = e.type.indexOf("?") == -1;
return (
<Item
name={`args.${e.name}`}
label={e.desc}
key={`args.${e.name}`}
valuePropName={e.type.indexOf("boolean") >= 0 ? 'checked' : undefined}
rules={[
{ required, message: `${e.desc}(${e.name})为必填字段` }
]}
>
{normalArgs(e)}
</Item>
)
})}
{customArgs()}
</div >
);
}
renderInputs(conf: BehaviorNodeTypeModel) {
if (!conf.input || !conf.input || conf.input.length == 0) {
return null;
}
return (
<div>
<Divider orientation="left"><h3>输入变量</h3></Divider>
{
conf.input.map((e, i) => {
return (
<Item
label={e}
name={`input.${i}`}
key={`input.${i}`}
>
<Input onBlur={this.handleSubmit} />
</Item>
)
})
} }
</div>
)
}
renderOutputs(conf: BehaviorNodeTypeModel) { // 普通参数
if (!conf.output || !conf.output || conf.output.length == 0) { const normalArgs = (e: ArgsDefType) => {
return null; const required = e.type.indexOf("?") == -1;
} if (e.type.indexOf("string") >= 0) {
return ( return <Input onBlur={this.handleSubmit} />;
<div> } else if (e.type.indexOf("int") >= 0) {
<Divider orientation="left"><h3>输出变量</h3></Divider> return <InputNumber style={{ width: "100%" }} onBlur={this.handleSubmit} />;
{ } else if (e.type.indexOf("boolean") >= 0) {
conf.output.map((e, i) => { return <Switch onChange={this.handleSubmit} />;
} else if (e.type.indexOf("lua") >= 0) {
return <Input onBlur={this.handleSubmit} placeholder={"公式"} />;
}
};
// 自定义参数
const customArgs = () => {
return ( return (
<Item <Item name="customArgs" label="自定义" key="customArgs">
label={e} <Input.TextArea onBlur={this.handleSubmit} style={{ minHeight: 100 }} />
name={`output.${i}`} </Item>
key={`output.${i}`} );
> };
<Input onBlur={this.handleSubmit} />
</Item>
)
})
}
</div>
)
}
return (
<div>
<Divider orientation="left">
<h3>常量参数</h3>
</Divider>
{conf &&
conf.args &&
conf.args.map((e, i: number) => {
const required = e.type.indexOf("?") == -1;
return (
<Item
name={`args.${e.name}`}
label={e.desc}
key={`args.${e.name}`}
valuePropName={
e.type.indexOf("boolean") >= 0 ? "checked" : undefined
}
rules={[{ required, message: `${e.desc}(${e.name})为必填字段` }]}
>
{normalArgs(e)}
</Item>
);
})}
{customArgs()}
</div>
);
}
renderInputs(conf: BehaviorNodeTypeModel) {
if (!conf.input || !conf.input || conf.input.length == 0) {
return null;
}
return (
<div>
<Divider orientation="left">
<h3>输入变量</h3>
</Divider>
{conf.input.map((e, i) => {
return (
<Item label={e} name={`input.${i}`} key={`input.${i}`}>
<Input onBlur={this.handleSubmit} />
</Item>
);
})}
</div>
);
}
renderOutputs(conf: BehaviorNodeTypeModel) {
if (!conf.output || !conf.output || conf.output.length == 0) {
return null;
}
return (
<div>
<Divider orientation="left">
<h3>输出变量</h3>
</Divider>
{conf.output.map((e, i) => {
return (
<Item label={e} name={`output.${i}`} key={`output.${i}`}>
<Input onBlur={this.handleSubmit} />
</Item>
);
})}
</div>
);
}
} }
+28 -33
View File
@@ -1,33 +1,28 @@
import React from 'react'; import React from "react";
import { Card } from 'antd'; import { Card } from "antd";
import { INode } from '@antv/g6/lib/interface/item'; import { INode } from "@antv/g6/lib/interface/item";
import { BehaviorTreeModel } from '../../common/BehaviorTreeModel'; import { BehaviorTreeModel } from "../../common/BehaviorTreeModel";
interface TreePanelProps { interface TreePanelProps {
model?: BehaviorTreeModel; model?: BehaviorTreeModel;
} }
interface TreePanelState { interface TreePanelState {}
}
export default class TreePanel extends React.Component<TreePanelProps, TreePanelState> {
export default class TreePanel extends React.Component<TreePanelProps, TreePanelState> { state: TreePanelState = {};
state: TreePanelState = {};
componentDidMount() {}
componentDidMount() {
} render() {
const { model } = this.props;
render() { if (!model) {
const { model } = this.props; return <div />;
if(!model) { }
return (<div/>) return (
} <Card title="概况" style={{ height: "100%" }}>
return ( 行为树:{model.name}
<Card title="概况" style={{ height: "100%" }}> </Card>
行为树:{model.name} );
}
</Card> }
)
}
}
+414 -412
View File
@@ -1,412 +1,414 @@
import * as React from 'react'; import * as React from "react";
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { Row, Col, message } from 'antd'; import { Row, Col, message } from "antd";
import NodePanel from './NodePanel'; 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 * as Utils from "../../common/Utils";
import { BehaviorTreeModel, GraphNodeModel, BehaviorNodeModel } from '../../common/BehaviorTreeModel'; import {
import TreePanel from './TreePanel'; BehaviorTreeModel,
import Settings from '../../main-process/Settings'; GraphNodeModel,
BehaviorNodeModel,
import './Editor.css'; } from "../../common/BehaviorTreeModel";
import { clipboard } from 'electron'; import TreePanel from "./TreePanel";
import Settings from "../../main-process/Settings";
export interface EditorProps {
filepath: string; import "./Editor.css";
} import { clipboard } from "electron";
interface EditorState { export interface EditorProps {
curNodeId?: string; filepath: string;
treeModel?: BehaviorTreeModel; }
settings?: Settings;
} interface EditorState {
curNodeId?: string;
export default class Editor extends React.Component<EditorProps, EditorState> { treeModel?: BehaviorTreeModel;
private ref: React.RefObject<any>; settings?: Settings;
state: EditorState = {}; }
private graph: TreeGraph; export default class Editor extends React.Component<EditorProps, EditorState> {
private dragSrcId: string; private ref: React.RefObject<any>;
private dragDstId: string; state: EditorState = {};
private autoId: number;
private graph: TreeGraph;
constructor(props: EditorProps) { private dragSrcId: string;
super(props); private dragDstId: string;
this.ref = React.createRef(); private autoId: number;
}
constructor(props: EditorProps) {
componentDidMount() { super(props);
const graph = new TreeGraph({ this.ref = React.createRef();
container: this.ref.current, }
width: window.screen.width * 0.66,
height: window.screen.height, componentDidMount() {
animate: false, const graph = new TreeGraph({
// fitCenter: true, container: this.ref.current,
modes: { width: window.screen.width * 0.66,
default: [ height: window.screen.height,
'drag-canvas', animate: false,
'zoom-canvas', // fitCenter: true,
'click-select', modes: {
'hover', default: [
{ "drag-canvas",
type: 'collapse-expand', "zoom-canvas",
trigger: 'dblclick', "click-select",
onChange: (item, collapsed) => { "hover",
this.onSelectNode(item.getID()); {
const data = item.getModel(); type: "collapse-expand",
data.collapsed = collapsed; trigger: "dblclick",
graph.setItemState(item, 'collapsed', data.collapsed as boolean); onChange: (item, collapsed) => {
const icon = data.collapsed ? G6.Marker.expand : G6.Marker.collapse; this.onSelectNode(item.getID());
const marker = item.get('group').find((ele: any) => ele.get('name') === 'collapse-icon'); const data = item.getModel();
marker.attr('symbol', icon); data.collapsed = collapsed;
return true; graph.setItemState(item, "collapsed", data.collapsed as boolean);
}, const icon = data.collapsed ? G6.Marker.expand : G6.Marker.collapse;
}, const marker = item
] .get("group")
}, .find((ele: any) => ele.get("name") === "collapse-icon");
defaultEdge: { marker.attr("symbol", icon);
type: 'cubic-horizontal', return true;
style: { },
stroke: '#A3B1BF' },
} ],
}, },
defaultNode: { defaultEdge: {
type: "TreeNode", type: "cubic-horizontal",
}, style: {
layout: { stroke: "#A3B1BF",
type: 'compactBox', },
direction: 'LR', },
getHGap: () => 50, defaultNode: {
getWidth: (d: GraphNodeModel) => { type: "TreeNode",
return 150; },
}, layout: {
getHeight: (d: GraphNodeModel) => { type: "compactBox",
if (d.size) { direction: "LR",
return d.size[1]; getHGap: () => 50,
} else { getWidth: (d: GraphNodeModel) => {
return 50; return 150;
} },
} getHeight: (d: GraphNodeModel) => {
} if (d.size) {
}); return d.size[1];
} else {
graph.on('node:mouseenter', (e: G6GraphEvent) => { return 50;
const { item } = e; }
if (item.hasState('selected')) { },
return; },
} });
graph.setItemState(item, 'hover', true);
}); graph.on("node:mouseenter", (e: G6GraphEvent) => {
const { item } = e;
graph.on('node:mouseleave', (e: G6GraphEvent) => { if (item.hasState("selected")) {
const { item } = e; return;
if (item.hasState('selected')) { }
return; graph.setItemState(item, "hover", true);
} });
graph.setItemState(item, 'hover', false);
}); graph.on("node:mouseleave", (e: G6GraphEvent) => {
const { item } = e;
graph.on('nodeselectchange', (e: G6GraphEvent) => { if (item.hasState("selected")) {
if (e.target) { return;
this.onSelectNode(e.target.getID()); }
} else { graph.setItemState(item, "hover", false);
this.onSelectNode(null); });
}
}); graph.on("nodeselectchange", (e: G6GraphEvent) => {
if (e.target) {
const clearDragDstState = () => { this.onSelectNode(e.target.getID());
if (this.dragDstId) { } else {
graph.setItemState(this.dragDstId, 'dragRight', false); this.onSelectNode(null);
graph.setItemState(this.dragDstId, 'dragDown', false); }
graph.setItemState(this.dragDstId, 'dragUp', false); });
this.dragDstId = null;
} const clearDragDstState = () => {
} if (this.dragDstId) {
graph.setItemState(this.dragDstId, "dragRight", false);
const clearDragSrcState = () => { graph.setItemState(this.dragDstId, "dragDown", false);
if (this.dragSrcId) { graph.setItemState(this.dragDstId, "dragUp", false);
graph.setItemState(this.dragSrcId, 'dragSrc', false); this.dragDstId = null;
this.dragSrcId = null; }
} };
}
const clearDragSrcState = () => {
graph.on('node:dragstart', (e: G6GraphEvent) => { if (this.dragSrcId) {
this.dragSrcId = e.item.getID(); graph.setItemState(this.dragSrcId, "dragSrc", false);
graph.setItemState(this.dragSrcId, 'dragSrc', true); this.dragSrcId = null;
}); }
graph.on('node:dragend', (e: G6GraphEvent) => { };
if (this.dragSrcId) {
graph.setItemState(this.dragSrcId, 'dragSrc', false); graph.on("node:dragstart", (e: G6GraphEvent) => {
this.dragSrcId = null; this.dragSrcId = e.item.getID();
} graph.setItemState(this.dragSrcId, "dragSrc", true);
}); });
graph.on("node:dragend", (e: G6GraphEvent) => {
graph.on('node:dragover', (e: G6GraphEvent) => { if (this.dragSrcId) {
const dstNodeId = e.item.getID(); graph.setItemState(this.dragSrcId, "dragSrc", false);
if (dstNodeId == this.dragSrcId) { this.dragSrcId = null;
return; }
} });
if (this.dragDstId) { graph.on("node:dragover", (e: G6GraphEvent) => {
graph.setItemState(this.dragDstId, 'dragRight', false); const dstNodeId = e.item.getID();
graph.setItemState(this.dragDstId, 'dragDown', false); if (dstNodeId == this.dragSrcId) {
graph.setItemState(this.dragDstId, 'dragUp', false); return;
} }
const box = e.item.getBBox(); if (this.dragDstId) {
if (e.x > box.minX + box.width * 0.6) { graph.setItemState(this.dragDstId, "dragRight", false);
graph.setItemState(dstNodeId, 'dragRight', true); graph.setItemState(this.dragDstId, "dragDown", false);
} else if (e.y > box.minY + box.height * 0.5) { graph.setItemState(this.dragDstId, "dragUp", false);
graph.setItemState(dstNodeId, 'dragDown', true); }
} else {
graph.setItemState(dstNodeId, 'dragUp', true); const box = e.item.getBBox();
} if (e.x > box.minX + box.width * 0.6) {
this.dragDstId = dstNodeId; graph.setItemState(dstNodeId, "dragRight", true);
}); } else if (e.y > box.minY + box.height * 0.5) {
graph.setItemState(dstNodeId, "dragDown", true);
graph.on('node:dragleave', (e: G6GraphEvent) => { } else {
clearDragDstState(); graph.setItemState(dstNodeId, "dragUp", true);
}) }
this.dragDstId = dstNodeId;
graph.on('node:drop', (e: G6GraphEvent) => { });
const srcNodeId = this.dragSrcId;
const dstNode = e.item; graph.on("node:dragleave", (e: G6GraphEvent) => {
clearDragDstState();
var dragDir; });
if (dstNode.hasState('dragRight')) {
dragDir = 'dragRight'; graph.on("node:drop", (e: G6GraphEvent) => {
} else if (dstNode.hasState('dragDown')) { const srcNodeId = this.dragSrcId;
dragDir = 'dragDown'; const dstNode = e.item;
} else if (dstNode.hasState('dragUp')) {
dragDir = 'dragUp'; var dragDir;
} if (dstNode.hasState("dragRight")) {
dragDir = "dragRight";
clearDragSrcState(); } else if (dstNode.hasState("dragDown")) {
clearDragDstState(); dragDir = "dragDown";
} else if (dstNode.hasState("dragUp")) {
if (!srcNodeId) { dragDir = "dragUp";
console.log("no drag src"); }
return;
} clearDragSrcState();
clearDragDstState();
if (srcNodeId == dstNode.getID()) {
console.log("drop same node"); if (!srcNodeId) {
return; console.log("no drag src");
} return;
}
const rootData = graph.findDataById('1');
const srcData = graph.findDataById(srcNodeId); if (srcNodeId == dstNode.getID()) {
const srcParent = Utils.findParent(rootData, srcNodeId); console.log("drop same node");
const dstData = graph.findDataById(dstNode.getID()); return;
const dstParent = Utils.findParent(rootData, dstNode.getID()); }
if (!srcParent) {
console.log("no parent!"); const rootData = graph.findDataById("1");
return; const srcData = graph.findDataById(srcNodeId);
} const srcParent = Utils.findParent(rootData, srcNodeId);
const dstData = graph.findDataById(dstNode.getID());
if (Utils.findFromAllChildren(srcData, dstData.id)) { const dstParent = Utils.findParent(rootData, dstNode.getID());
// 不能将父节点拖到自已的子孙节点 if (!srcParent) {
console.log("cannot move to child"); console.log("no parent!");
return; return;
} }
const removeSrc = () => { if (Utils.findFromAllChildren(srcData, dstData.id)) {
srcParent.children = srcParent.children.filter(e => e.id != srcData.id); // 不能将父节点拖到自已的子孙节点
} console.log("cannot move to child");
console.log("dstNode", dstNode); return;
if (dragDir == 'dragRight') { }
removeSrc();
if (!dstData.children) { const removeSrc = () => {
dstData.children = []; srcParent.children = srcParent.children.filter((e) => e.id != srcData.id);
} };
dstData.children.push(srcData); console.log("dstNode", dstNode);
} else if (dragDir == 'dragUp') { if (dragDir == "dragRight") {
if (!dstParent) { removeSrc();
return; if (!dstData.children) {
} dstData.children = [];
removeSrc(); }
const idx = dstParent.children.findIndex(e => e.id == dstData.id); dstData.children.push(srcData);
dstParent.children.splice(idx, 0, srcData); } else if (dragDir == "dragUp") {
} else if (dragDir == 'dragDown') { if (!dstParent) {
if (!dstParent) { return;
return; }
} removeSrc();
removeSrc(); const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
const idx = dstParent.children.findIndex(e => e.id == dstData.id); dstParent.children.splice(idx, 0, srcData);
dstParent.children.splice(idx + 1, 0, srcData); } else if (dragDir == "dragDown") {
} else { if (!dstParent) {
return; return;
} }
removeSrc();
// console.log("cur data", graph.findDataById('1')); const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
this.changeWithoutAnim(); dstParent.children.splice(idx + 1, 0, srcData);
}); } else {
return;
const settings = Utils.getRemoteSettings(); }
const str = fs.readFileSync(this.props.filepath, 'utf8'); // console.log("cur data", graph.findDataById('1'));
let tree: BehaviorTreeModel = JSON.parse(str); this.changeWithoutAnim();
const data = Utils.createTreeData(tree.root, settings); });
this.autoId = Utils.refreshNodeId(data);
graph.data(data); const settings = Utils.getRemoteSettings();
graph.render();
graph.fitCenter(); const str = fs.readFileSync(this.props.filepath, "utf8");
graph.set('animate', true); let tree: BehaviorTreeModel = JSON.parse(str);
const data = Utils.createTreeData(tree.root, settings);
this.graph = graph; this.autoId = Utils.refreshNodeId(data);
graph.data(data);
this.setState({ treeModel: tree, settings }); graph.render();
} graph.fitCenter();
graph.set("animate", true);
onSelectNode(curNodeId: string | null) {
const graph = this.graph; this.graph = graph;
if (this.state.curNodeId) { this.setState({ treeModel: tree, settings });
graph.setItemState(this.state.curNodeId, 'selected', false); }
}
onSelectNode(curNodeId: string | null) {
this.setState({ curNodeId }); const graph = this.graph;
if (this.state.curNodeId) {
graph.setItemState(this.state.curNodeId, 'selected', true); if (this.state.curNodeId) {
} graph.setItemState(this.state.curNodeId, "selected", false);
} }
createNode(name: string) { this.setState({ curNodeId });
console.log("editor create node", name); if (this.state.curNodeId) {
const { curNodeId, settings } = this.state; graph.setItemState(this.state.curNodeId, "selected", true);
if (!curNodeId) { }
message.warn("未选中节点"); }
return;
} createNode(name: string) {
const curNodeData = this.graph.findDataById(curNodeId); console.log("editor create node", name);
const newNodeData: BehaviorNodeModel = { const { curNodeId, settings } = this.state;
id: this.autoId++, if (!curNodeId) {
name: name, message.warn("未选中节点");
} return;
if (!curNodeData.children) { }
curNodeData.children = []; const curNodeData = this.graph.findDataById(curNodeId);
} const newNodeData: BehaviorNodeModel = {
curNodeData.children.push(Utils.createTreeData(newNodeData, settings)); id: this.autoId++,
this.changeWithoutAnim(); name: name,
} };
if (!curNodeData.children) {
deleteNode() { curNodeData.children = [];
console.log("editor delete node"); }
const { curNodeId } = this.state; curNodeData.children.push(Utils.createTreeData(newNodeData, settings));
if (!curNodeId) { this.changeWithoutAnim();
return; }
}
deleteNode() {
if(curNodeId == '1') { console.log("editor delete node");
message.warn("根节点不能删除!"); const { curNodeId } = this.state;
return; if (!curNodeId) {
} return;
}
this.onSelectNode(null);
const rootData = this.graph.findDataById('1'); if (curNodeId == "1") {
const parentData = Utils.findParent(rootData, curNodeId); message.warn("根节点不能删除!");
parentData.children = parentData.children.filter(e => e.id != curNodeId); return;
this.changeWithoutAnim(); }
}
this.onSelectNode(null);
updateNode(id: string) { const rootData = this.graph.findDataById("1");
this.graph.changeData(); const parentData = Utils.findParent(rootData, curNodeId);
this.graph.layout(); parentData.children = parentData.children.filter((e) => e.id != curNodeId);
} this.changeWithoutAnim();
}
changeWithoutAnim() {
this.graph.set('animate', false); updateNode(id: string) {
this.graph.changeData(); this.graph.changeData();
this.graph.layout(); this.graph.layout();
this.graph.set('animate', true); }
}
changeWithoutAnim() {
save() { this.graph.set("animate", false);
const { filepath } = this.props; this.graph.changeData();
const root = Utils.createFileData(this.graph.findDataById('1') as GraphNodeModel); this.graph.layout();
fs.writeFileSync(filepath, JSON.stringify({ this.graph.set("animate", true);
name: path.basename(filepath).slice(0, -5), }
root,
desc: root.desc, save() {
})); const { filepath } = this.props;
} const root = Utils.createFileData(this.graph.findDataById("1") as GraphNodeModel);
fs.writeFileSync(
copyNode() { filepath,
console.log("editor copy node"); JSON.stringify({
const { curNodeId } = this.state; name: path.basename(filepath).slice(0, -5),
if (!curNodeId) { root,
return; desc: root.desc,
} })
const data = this.graph.findDataById(curNodeId); );
clipboard.writeText(JSON.stringify(data, null, 2)); }
}
copyNode() {
pasteNode() { console.log("editor copy node");
const { curNodeId } = this.state; const { curNodeId } = this.state;
if (!curNodeId) { if (!curNodeId) {
message.warn("未选中节点"); return;
return; }
} const data = this.graph.findDataById(curNodeId);
const curNodeData = this.graph.findDataById(curNodeId); clipboard.writeText(JSON.stringify(data, null, 2));
try { }
const str = clipboard.readText();
if(!str || str == '') { pasteNode() {
return; const { curNodeId } = this.state;
} if (!curNodeId) {
const data = JSON.parse(str); message.warn("未选中节点");
if(!curNodeData.children) { return;
curNodeData.children = []; }
} const curNodeData = this.graph.findDataById(curNodeId);
curNodeData.children.push(data); try {
this.autoId = Utils.refreshNodeId(this.graph.findDataById('1') as GraphNodeModel); const str = clipboard.readText();
this.changeWithoutAnim(); if (!str || str == "") {
} catch (error) { return;
message.error("粘贴数据有误"); }
} const data = JSON.parse(str);
} if (!curNodeData.children) {
curNodeData.children = [];
render() { }
const { curNodeId, treeModel, settings } = this.state; curNodeData.children.push(data);
var curNode: any; this.autoId = Utils.refreshNodeId(this.graph.findDataById("1") as GraphNodeModel);
if (curNodeId) { this.changeWithoutAnim();
curNode = this.graph.findDataById(curNodeId); } catch (error) {
} message.error("粘贴数据有误");
}
return ( }
<div className="editor">
<Row className="editorBd"> render() {
<Col const { curNodeId, treeModel, settings } = this.state;
span={18} var curNode: any;
className="editorContent" if (curNodeId) {
ref={this.ref} curNode = this.graph.findDataById(curNodeId);
/> }
<Col span={6} className="editorSidebar">
{ return (
curNode ? <div className="editor">
<NodePanel <Row className="editorBd">
model={curNode} <Col span={18} className="editorContent" ref={this.ref} />
settings={settings} <Col span={6} className="editorSidebar">
updateNode={(id, forceUpdate) => { {curNode ? (
if (forceUpdate) { <NodePanel
const data: any = this.graph.findDataById(id); model={curNode}
data.conf = settings.getNodeConf(data.name); settings={settings}
data.size = Utils.calcTreeNodeSize(data); updateNode={(id, forceUpdate) => {
this.changeWithoutAnim(); if (forceUpdate) {
} const data: any = this.graph.findDataById(id);
const item = this.graph.findById(id); data.conf = settings.getNodeConf(data.name);
item.draw(); data.size = Utils.calcTreeNodeSize(data);
}} this.changeWithoutAnim();
/> }
: const item = this.graph.findById(id);
<TreePanel item.draw();
model={treeModel} }}
/> />
} ) : (
</Col> <TreePanel model={treeModel} />
</Row> )}
</div> </Col>
); </Row>
} </div>
} );
}
}
+89 -99
View File
@@ -1,99 +1,89 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { Menu, Input } from "antd"; import { Menu, Input } from "antd";
import * as fs from 'fs'; import * as fs from "fs";
import * as path from 'path'; import * as path from "path";
import { ipcRenderer } from "electron"; import { ipcRenderer } from "electron";
import MainEventType from "../common/MainEventType"; import MainEventType from "../common/MainEventType";
const { Search } = Input; const { Search } = Input;
export interface TreeFile { export interface TreeFile {
name: string; name: string;
path: string; path: string;
} }
export interface PropertiesProps { export interface PropertiesProps {
workdir: string; workdir: string;
onOpenTree: (path: string) => void; onOpenTree: (path: string) => void;
} }
interface PropertiesState { interface PropertiesState {
treeList: TreeFile[] treeList: TreeFile[];
} }
export default class Properties extends Component<PropertiesProps> { export default class Properties extends Component<PropertiesProps> {
state: PropertiesState = { state: PropertiesState = {
treeList: [] treeList: [],
}; };
curWorkdir: string = ''; curWorkdir: string = "";
shouldComponentUpdate(nextProps: PropertiesProps) { shouldComponentUpdate(nextProps: PropertiesProps) {
const shouldUpdate = this.curWorkdir != nextProps.workdir; const shouldUpdate = this.curWorkdir != nextProps.workdir;
this.curWorkdir = nextProps.workdir; this.curWorkdir = nextProps.workdir;
return shouldUpdate; return shouldUpdate;
} }
componentDidMount() { componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_TREE, (event: any, path: string) => { ipcRenderer.on(MainEventType.CREATE_TREE, (event: any, path: string) => {
console.log("on Create tree", path); console.log("on Create tree", path);
this.props.onOpenTree(path); this.props.onOpenTree(path);
this.forceUpdate(); this.forceUpdate();
}); });
var workdir = this.props.workdir; var workdir = this.props.workdir;
if (workdir == '') { if (workdir == "") {
return; return;
} }
this.setState({ treeList: this.getTreeList(workdir) }); this.setState({ treeList: this.getTreeList(workdir) });
} }
getTreeList(workdir: string) { getTreeList(workdir: string) {
if (workdir == '' || !fs.existsSync(workdir)) { if (workdir == "" || !fs.existsSync(workdir)) {
console.log("workdir not exist", workdir); console.log("workdir not exist", workdir);
return []; return [];
} }
const files = fs.readdirSync(workdir); const files = fs.readdirSync(workdir);
const list: TreeFile[] = []; const list: TreeFile[] = [];
files.forEach((filename) => { files.forEach((filename) => {
const stat = fs.statSync(path.join(workdir, filename)) const stat = fs.statSync(path.join(workdir, filename));
if (stat.isFile()) { if (stat.isFile()) {
var name = filename.slice(0, -5) var name = filename.slice(0, -5);
list.push({ list.push({
name, name,
path: workdir + '/' + name + '.json' path: workdir + "/" + name + ".json",
}); });
} }
}) });
return list; return list;
} }
render() { render() {
console.log("render Properties"); console.log("render Properties");
const { onOpenTree, workdir } = this.props; const { onOpenTree, workdir } = this.props;
const treeList = this.getTreeList(workdir); const treeList = this.getTreeList(workdir);
return ( return (
<div> <div>
<Search <Search placeholder="Search" onChange={() => {}} />
placeholder="Search" <Menu mode="inline">
onChange={() => { {treeList.map((tree) => (
<Menu.Item key={tree.name} onClick={() => onOpenTree(tree.path)}>
}} {tree.name}
/> </Menu.Item>
<Menu ))}
mode="inline" </Menu>
> </div>
{treeList.map((tree) => ( );
<Menu.Item }
key={tree.name} }
onClick={() => onOpenTree(tree.path)}
>
{tree.name}
</Menu.Item>
))}
</Menu>
</div>
)
}
}
+275 -277
View File
@@ -1,277 +1,275 @@
import G6 from '@antv/g6'; import G6 from "@antv/g6";
import Settings from '../main-process/Settings'; import Settings from "../main-process/Settings";
const NODE_COLORS: any = { const NODE_COLORS: any = {
['Composite']: 'rgb(91,237,32)', ["Composite"]: "rgb(91,237,32)",
['Decorator']: 'rgb(218,167,16)', ["Decorator"]: "rgb(218,167,16)",
['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)",
} };
export default function (settings: Settings) {
export default function (settings: Settings) { const collapseStyle: any = {
const collapseStyle: any = { "collapse-icon": {
'collapse-icon': { // symbol: G6.Marker.expand,
// symbol: G6.Marker.expand, fill: "blue",
fill: 'blue', },
} };
}; G6.registerNode(
G6.registerNode( "TreeNode",
'TreeNode', {
{ options: {
options: { type: "rect",
type: 'rect', labelCfg: {
labelCfg: { style: {
style: { fill: "blue",
fill: 'blue', fontSize: 10,
fontSize: 10 },
} },
}, style: {
style: { fill: "white",
fill: "white", },
}, stateStyles: {
stateStyles: { selected: {
selected: { "main-box": {
'main-box': { stroke: "yellow",
stroke: 'yellow', lineWidth: 5,
lineWidth: 5, },
}, },
}, hover: {
hover: { stroke: "white",
stroke: 'white', lineWidth: 3,
lineWidth: 3, },
}, dragSrc: {
dragSrc: { fill: "gray",
fill: 'gray', },
}, dragRight: {
dragRight: { "drag-right": {
'drag-right': { fillOpacity: 0.6,
fillOpacity: 0.6, },
} },
}, dragUp: {
dragUp: { "drag-up": {
'drag-up': { fillOpacity: 0.6,
fillOpacity: 0.6, },
} },
}, dragDown: {
dragDown: { "drag-down": {
'drag-down': { fillOpacity: 0.6,
fillOpacity: 0.6, },
} },
} },
}, },
}, 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";
const classify = nodeConf.type || 'Other'; 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 x0 = -w / 2; const y0 = -h / 2;
const y0 = -h / 2; const r = 2;
const r = 2; const shape = group.addShape("rect", {
const shape = group.addShape('rect', { attrs: {
attrs: { x: x0,
x: x0, y: y0,
y: y0, width: w,
width: w, height: h,
height: h, stroke: color,
stroke: color, lineWidth: 2,
lineWidth: 2, fill: "white",
fill: 'white', radius: r,
radius: r, },
}, name: "main-box",
name: 'main-box', draggable: true,
draggable: true, });
});
// name bg
// name bg group.addShape("rect", {
group.addShape('rect', { attrs: {
attrs: { x: x0 + 1.5,
x: x0 + 1.5, y: y0 + 1.5,
y: y0 + 1.5, width: w - 3,
width: w - 3, height: 16,
height: 16, fill: color,
fill: color, // radius: r,
// radius: r, },
}, name: "name-bg",
name: 'name-bg', draggable: true,
draggable: true, });
});
// id text
// id text group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x: x0 - 3,
x: x0 - 3, y: -8,
y: -8, fontSize: 20,
fontSize: 20, lineHeight: 20,
lineHeight: 20, text: cfg.id,
text: cfg.id, textAlign: "right",
textAlign: 'right', fill: "white",
fill: 'white', stroke: "black",
stroke: 'black', lineWidth: 2,
lineWidth: 2, },
}, name: "id-text",
name: 'id-text', });
});
// icon
// icon var img = `./static/icons/${classify}.svg`;
var img = `./static/icons/${classify}.svg`; group.addShape("image", {
group.addShape('image', { attrs: {
attrs: { x: x0 + 3,
x: x0 + 3, y: y0 + 2,
y: y0 + 2, height: 14,
height: 14, width: 14,
width: 14, img,
img, },
}, name: "node-icon",
name: 'node-icon', });
});
// name text
// name text group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x: x0 + 18,
x: x0 + 18, y: y0 + 4,
y: y0 + 4, fontWeight: 800,
fontWeight: 800, lineHeight: 20,
lineHeight: 20, text: cfg.name,
text: cfg.name, fill: "black",
fill: 'black', },
}, name: "name-text",
name: 'name-text', });
});
var x = x0 + 2;
var x = x0 + 2; var y = y0 + 23;
var y = y0 + 23; // desc text
// desc text const desc = cfg.desc || nodeConf.desc;
const desc = cfg.desc || nodeConf.desc; if (desc) {
if (desc) { group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x,
x, y,
y, lineHeight: 20,
lineHeight: 20, fontWeight: 800,
fontWeight: 800, text: `备注:${desc}`,
text: `备注:${desc}`, fill: "black",
fill: 'black', },
}, name: "desc-text",
name: 'desc-text', });
}); }
}
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) { y += 20;
y += 20; group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x,
x, y,
y, lineHeight: 20,
lineHeight: 20, text: `参数:${JSON.stringify(args)}`,
text: `参数:${JSON.stringify(args)}`, fill: "black",
fill: 'black', },
}, name: "args-text",
name: 'args-text', });
}); }
}
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) { y += 20;
y += 20; group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x,
x, y,
y, lineHeight: 20,
lineHeight: 20, text: `输入:${JSON.stringify(input)}`,
text: `输入:${JSON.stringify(input)}`, fill: "black",
fill: 'black', },
}, name: "input-text",
name: 'input-text', });
}); }
}
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) { y += 20;
y += 20; group.addShape("text", {
group.addShape('text', { attrs: {
attrs: { textBaseline: "top",
textBaseline: 'top', x,
x, y,
y, lineHeight: 20,
lineHeight: 20, text: `输出:${JSON.stringify(output)}`,
text: `输出:${JSON.stringify(output)}`, fill: "black",
fill: 'black', },
}, name: "output-text",
name: 'output-text', });
}); }
}
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 / 2, y: 0,
y: 0, r: 6,
r: 6, symbol: G6.Marker.collapse,
symbol: G6.Marker.collapse, stroke: "#666",
stroke: '#666', lineWidth: 1,
lineWidth: 1, fill: "#fff",
fill: '#fff', },
}, name: "collapse-icon",
name: 'collapse-icon', });
}); }
}
group.addShape("rect", {
name: "drag-up",
group.addShape('rect', { attrs: {
name: 'drag-up', x: -w / 2,
attrs: { y: -h / 2,
x: -w / 2, width: w,
y: -h / 2, height: h / 2,
width: w, fill: "blue",
height: h / 2, fillOpacity: 0,
fill: 'blue', },
fillOpacity: 0, draggable: true,
}, // visible: false,
draggable: true, });
// visible: false,
}); group.addShape("rect", {
name: "drag-down",
group.addShape('rect', { attrs: {
name: 'drag-down', x: -w / 2,
attrs: { y: 0,
x: -w / 2, width: w,
y: 0, height: h / 2,
width: w, fill: "blue",
height: h / 2, fillOpacity: 0,
fill: 'blue', },
fillOpacity: 0, draggable: true,
}, // visible: false,
draggable: true, });
// visible: false,
}); group.addShape("rect", {
name: "drag-right",
group.addShape('rect', { attrs: {
name: 'drag-right', x: w * 0.1,
attrs: { y: -h / 2,
x: w * 0.1, width: w * 0.4,
y: -h / 2, height: h,
width: w * 0.4, fill: "blue",
height: h, fillOpacity: 0,
fill: 'blue', },
fillOpacity: 0, draggable: true,
}, // visible: false,
draggable: true, });
// visible: false, return shape;
}); },
return shape; },
} "single-node"
}, );
'single-node', }
);
}
+130 -136
View File
@@ -1,136 +1,130 @@
import Editor from "./Editor"; import Editor from "./Editor";
import React, { Component } from "react"; import React, { Component } from "react";
import { Layout, Tabs, message } from 'antd'; import { Layout, Tabs, message } from "antd";
import { ipcRenderer, remote } from 'electron'; import { ipcRenderer, remote } from "electron";
import * as path from 'path'; import * as path from "path";
import 'antd/dist/antd.dark.css'; import "antd/dist/antd.dark.css";
import MainEventType from "../common/MainEventType"; import MainEventType from "../common/MainEventType";
const { TabPane } = Tabs; const { TabPane } = Tabs;
interface TreeTabsProps { interface TreeTabsProps {}
}
interface TreeTabsState {
interface TreeTabsState { filepaths: string[];
filepaths: string[]; curPath?: string;
curPath?: string; }
}
export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> { state: TreeTabsState = {
state: TreeTabsState = { filepaths: [],
filepaths: [], };
}
editors: { [path: string]: Editor } = {};
editors: { [path: string]: Editor } = {}
componentWillMount() {}
componentWillMount() {
} componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_NODE, (event: any, name: any) => {
componentDidMount() { const editor = this.getCurEditor();
ipcRenderer.on(MainEventType.CREATE_NODE, (event: any, name: any) => { editor?.createNode(name);
const editor = this.getCurEditor(); });
editor?.createNode(name);
}); ipcRenderer.on(MainEventType.DELETE_NODE, () => {
const editor = this.getCurEditor();
ipcRenderer.on(MainEventType.DELETE_NODE, () => { editor?.deleteNode();
const editor = this.getCurEditor(); });
editor?.deleteNode();
}); ipcRenderer.on(MainEventType.COPY_NODE, () => {
const editor = this.getCurEditor();
ipcRenderer.on(MainEventType.COPY_NODE, () => { editor?.copyNode();
const editor = this.getCurEditor(); });
editor?.copyNode();
}); ipcRenderer.on(MainEventType.PASTE_NODE, () => {
const editor = this.getCurEditor();
ipcRenderer.on(MainEventType.PASTE_NODE, () => { editor?.pasteNode();
const editor = this.getCurEditor(); });
editor?.pasteNode();
}); ipcRenderer.on(MainEventType.SAVE, (event: any) => {
const { curPath } = this.state;
ipcRenderer.on(MainEventType.SAVE, (event: any) => { if (!curPath) {
const { curPath } = this.state; return;
if (!curPath) { }
return; const editor = this.editors[curPath];
} editor.save();
const editor = this.editors[curPath]; message.success("已保存");
editor.save(); });
message.success("已保存");
}); ipcRenderer.on(MainEventType.SAVE_ALL, (event: any) => {
for (let k in this.editors) {
ipcRenderer.on(MainEventType.SAVE_ALL, (event: any) => { let editor = this.editors[k];
for (let k in this.editors) { editor.save();
let editor = this.editors[k]; }
editor.save(); message.success("已保存所有行为树");
} });
message.success("已保存所有行为树"); }
});
} getCurEditor() {
const { curPath } = this.state;
getCurEditor() { if (!curPath) {
const { curPath } = this.state; return;
if (!curPath) { }
return; return this.editors[curPath];
} }
return this.editors[curPath];
} openFile(path: string) {
console.log("on open file", path);
openFile(path: string) { if (this.state.filepaths.indexOf(path) < 0) {
console.log("on open file", path); const filepaths = this.state.filepaths;
if (this.state.filepaths.indexOf(path) < 0) { filepaths.push(path);
const filepaths = this.state.filepaths; this.setState({ filepaths, curPath: path });
filepaths.push(path); } else {
this.setState({ filepaths, curPath: path }); this.setState({ curPath: path });
} else { }
this.setState({ curPath: path }); }
}
} render() {
console.log("render tabs");
render() { const { filepaths, curPath } = this.state;
console.log("render tabs"); if (!curPath) {
const { filepaths, curPath } = this.state; return <div />;
if (!curPath) { }
return (<div />); return (
} <Tabs
return ( hideAdd
<Tabs className="tabs"
hideAdd type="editable-card"
className="tabs" defaultActiveKey={curPath}
type="editable-card" activeKey={curPath}
defaultActiveKey={curPath} onChange={(activeKey) => {
activeKey={curPath} this.setState({ curPath: activeKey });
onChange={activeKey => { }}
this.setState({ curPath: activeKey }) onEdit={(targetKey, action) => {
}} if (action == "remove") {
onEdit={(targetKey, action) => { const idx = filepaths.indexOf(targetKey as string);
if (action == 'remove') { if (idx < 0) {
const idx = filepaths.indexOf(targetKey as string); return;
if (idx < 0) { }
return; filepaths.splice(idx, 1);
} const nextPath = filepaths[idx - 1];
filepaths.splice(idx, 1); this.setState({ filepaths, curPath: nextPath });
const nextPath = filepaths[idx - 1]; }
this.setState({ filepaths, curPath: nextPath }); }}
} >
}} {filepaths.map((filepath) => {
> return (
{ <TabPane tab={path.basename(filepath)} key={filepath}>
filepaths.map(filepath => { <Editor
return ( filepath={filepath}
<TabPane tab={path.basename(filepath)} key={filepath}> ref={(ref) => {
<Editor this.editors[filepath] = ref;
filepath={filepath} }}
ref={ />
ref => { </TabPane>
this.editors[filepath] = ref; );
} })}
} </Tabs>
/> );
</TabPane> }
); }
})
}
</Tabs>
)
}
};
+90 -95
View File
@@ -1,95 +1,90 @@
import * as ReactDOM from "react-dom";
import * as ReactDOM from "react-dom"; import Editor from "./Editor";
import Editor from "./Editor"; import React, { Component } from "react";
import React, { Component } from "react"; import { Layout, Tabs } from "antd";
import { Layout, Tabs } from 'antd'; import { ipcRenderer, remote } from "electron";
import { ipcRenderer, remote } from 'electron'; import * as path from "path";
import * as path from 'path'; import * as fs from "fs";
import * as fs from 'fs'; import * as Utils from "../common/Utils";
import * as Utils from '../common/Utils'; import MainEventType from "../common/MainEventType";
import MainEventType from "../common/MainEventType"; import Properties from "./Properties";
import Properties from "./Properties"; import Settings from "../main-process/Settings";
import Settings from "../main-process/Settings"; import G6 from "@antv/g6";
import G6 from '@antv/g6';
import "antd/dist/antd.dark.css";
import 'antd/dist/antd.dark.css'; import "./index.css";
import './index.css'; import { ModelConfig, Item } from "@antv/g6/lib/types";
import { ModelConfig, Item } from "@antv/g6/lib/types"; import RegisterNode from "./RegisterNode";
import RegisterNode from "./RegisterNode"; import TreeTabs from "./TreeTabs";
import TreeTabs from "./TreeTabs";
const { Header, Sider, Content, Footer } = Layout;
const { Header, Sider, Content, Footer } = Layout; const { TabPane } = Tabs;
const { TabPane } = Tabs;
interface MainState {
workdir: string;
interface MainState { workspace: string;
workdir: string; }
workspace: string;
} export default class Main extends Component {
state: MainState = {
export default class Main extends Component { workdir: "",
state: MainState = { workspace: "",
workdir: '', };
workspace: '',
} settings: Settings;
tabs: TreeTabs;
settings: Settings;
tabs: TreeTabs; componentWillMount() {
this.updateSettings();
componentWillMount() { RegisterNode(this.settings);
this.updateSettings(); }
RegisterNode(this.settings);
} componentDidMount() {
ipcRenderer.on(MainEventType.OPEN_FILE, (event: any, path: any) => {
componentDidMount() { this.setState({ curPath: path });
ipcRenderer.on(MainEventType.OPEN_FILE, (event: any, path: any) => { });
this.setState({ curPath: path });
}); ipcRenderer.on(MainEventType.OPEN_DIR, (event: any, workdir: any, workspace: string) => {
console.log("on open workspace", workspace);
ipcRenderer.on(MainEventType.OPEN_DIR, (event: any, workdir: any, workspace: string) => { document.title = workspace;
console.log("on open workspace", workspace); this.setState({ workdir, workspace });
document.title = workspace; });
this.setState({ workdir, workspace });
}); console.log("workdir", this.settings.curWorkspace.getWorkdir());
this.setState({
console.log("workdir", this.settings.curWorkspace.getWorkdir()); workdir: this.settings.curWorkspace.getWorkdir(),
this.setState({ workspace: this.settings.curWorkspace.getFilepath(),
workdir: this.settings.curWorkspace.getWorkdir(), });
workspace: this.settings.curWorkspace.getFilepath(), }
});
} updateSettings() {
this.settings = Utils.getRemoteSettings();
updateSettings() { }
this.settings = Utils.getRemoteSettings();
} render() {
console.log("render main");
render() { const { workdir, workspace } = this.state;
console.log("render main"); document.title = `行为树编辑器 - ${workspace}`;
const { workdir, workspace } = this.state; return (
document.title = `行为树编辑器 - ${workspace}`; <Layout className="body">
return ( <Sider className="sider" width={250}>
<Layout className="body"> <Properties
<Sider className="sider" width={250}> workdir={workdir}
<Properties onOpenTree={(path) => {
workdir={workdir} this.tabs.openFile(path);
onOpenTree={(path) => { }}
this.tabs.openFile(path); />
}} </Sider>
/> <Content className="content">
</Sider> <TreeTabs
<Content className="content"> ref={(ref) => {
<TreeTabs this.tabs = ref;
ref={ref => { }}
this.tabs = ref; />
}} </Content>
/> </Layout>
</Content> );
</Layout> }
) }
}
}; ReactDOM.render(<Main />, document.getElementById("root") as HTMLElement);
ReactDOM.render(
<Main />,
document.getElementById('root') as HTMLElement
);