feat: export all trees to single json file support

This commit is contained in:
duanhongbo committed 2020-11-12 16:45:43 +08:00
1 parent f8cefaec6b
commit 637fd4a6f8
2 files changed
+473 -412

No files matched your search

+356 -326
View File
@@ -1,345 +1,375 @@
import { Menu, app, dialog, BrowserWindow, MenuItem, WebContents, MenuItemConstructorOptions } from 'electron';
import * as fs from 'fs';
import * as Utils from '../common/Utils';
import MainEventType from '../common/MainEventType';
import { MainProcess } from './MainProcess';
import Settings from './Settings';
import {
Menu,
app,
dialog,
BrowserWindow,
MenuItem,
WebContents,
MenuItemConstructorOptions,
} from "electron";
import * as fs from "fs";
import * as Utils from "../common/Utils";
import MainEventType from "../common/MainEventType";
import { MainProcess } from "./MainProcess";
import Settings from "./Settings";
export default class AppMenu {
private mainProcess: MainProcess;
private mainWindow: BrowserWindow;
private webContents: WebContents;
private settings: Settings;
private mainProcess: MainProcess;
private mainWindow: BrowserWindow;
private webContents: WebContents;
private settings: Settings;
constructor(mainProcess: MainProcess) {
this.mainProcess = mainProcess;
this.mainWindow = mainProcess.mainWindow;
this.webContents = mainProcess.mainWindow.webContents;
this.settings = mainProcess.settings;
}
createMenu() {
const menu: Menu = new Menu();
menu.append(this.createWorkspaceMenu());
menu.append(this.createFileMenu());
menu.append(this.createNodeMenu());
menu.append(this.createToolsMenu());
return menu;
}
private createFileMenu() {
const fileItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentFiles) {
fileItems.push({
label: path,
click: () => {
console.log("open recent file", path);
}
})
constructor(mainProcess: MainProcess) {
this.mainProcess = mainProcess;
this.mainWindow = mainProcess.mainWindow;
this.webContents = mainProcess.mainWindow.webContents;
this.settings = mainProcess.settings;
}
createMenu() {
const menu: Menu = new Menu();
menu.append(this.createWorkspaceMenu());
menu.append(this.createFileMenu());
menu.append(this.createNodeMenu());
menu.append(this.createToolsMenu());
return menu;
}
return new MenuItem({
label: "行为树",
submenu: [
{
label: "新建",
accelerator: 'ctrl+n',
click: () => {
(async () => {
const res = await dialog.showSaveDialog({
properties: ['showOverwriteConfirmation'],
filters: [
{ name: "Json", extensions: ['json'] }
]
});
if (!res.canceled) {
const path = res.filePath;
fs.writeFileSync(path, JSON.stringify(Utils.createNewTree(path), null, 2));
this.webContents.send(MainEventType.CREATE_TREE, path);
}
})();
},
},
{
label: "打开文件",
accelerator: "Ctrl+O",
click: () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: "Json", extensions: ['json'] }
]
});
if (res.filePaths.length > 0) {
const path = res.filePaths[0];
if (this.settings.recentFiles.indexOf(path) < 0) {
this.settings.recentFiles.unshift(path);
this.settings.save();
this.mainProcess.rebuildMenu();
}
this.webContents.send(MainEventType.OPEN_FILE, path);
}
})();
}
},
{ type: 'separator' },
{
label: "最近打开",
submenu: fileItems,
},
{ type: 'separator' },
{
label: "保存",
accelerator: 'ctrl+s',
click: () => {
this.webContents.send(MainEventType.SAVE);
},
},
{
label: "全部保存",
accelerator: 'ctrl+shift+s',
click: () => {
this.webContents.send(MainEventType.SAVE_ALL);
},
},
{ type: 'separator' },
{
label: '新建节点',
accelerator: 'insert',
click: () => {
this.webContents.send(MainEventType.CREATE_NODE, 'unknow');
}
},
{
label: '删除节点',
accelerator: 'delete',
click: () => {
this.webContents.send(MainEventType.DELETE_NODE);
}
},
{ type: 'separator' },
{
label: '复制节点',
accelerator: 'ctrl+c',
role: 'copy',
click: () => {
this.webContents.send(MainEventType.COPY_NODE);
}
},
{
label: '粘贴节点',
accelerator: 'ctrl+v',
click: () => {
this.webContents.send(MainEventType.PASTE_NODE);
}
private createFileMenu() {
const fileItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentFiles) {
fileItems.push({
label: path,
click: () => {
console.log("open recent file", path);
},
});
}
]
});
}
private createWorkspaceMenu() {
const openWorkspace = (path: string) => {
const curWorkspace = this.settings.curWorkspace;
curWorkspace.setFilepath(path);
curWorkspace.load();
this.settings.pushRecentWorkspace(path);
this.mainProcess.rebuildMenu();
this.webContents.send(MainEventType.OPEN_DIR, curWorkspace.getWorkdir(), curWorkspace.getFilepath());
};
const saveToNewPath = () => {
(async () => {
const res = await dialog.showSaveDialog({
properties: ['showOverwriteConfirmation'],
filters: [
{ name: "Json", extensions: ['json'] }
]
return new MenuItem({
label: "行为树",
submenu: [
{
label: "新建",
accelerator: "ctrl+n",
click: () => {
(async () => {
const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (!res.canceled) {
const path = res.filePath;
fs.writeFileSync(
path,
JSON.stringify(Utils.createNewTree(path), null, 2)
);
this.webContents.send(MainEventType.CREATE_TREE, path);
}
})();
},
},
{
label: "打开文件",
accelerator: "Ctrl+O",
click: () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (res.filePaths.length > 0) {
const path = res.filePaths[0];
if (this.settings.recentFiles.indexOf(path) < 0) {
this.settings.recentFiles.unshift(path);
this.settings.save();
this.mainProcess.rebuildMenu();
}
this.webContents.send(MainEventType.OPEN_FILE, path);
}
})();
},
},
{ type: "separator" },
{
label: "最近打开",
submenu: fileItems,
},
{ type: "separator" },
{
label: "保存",
accelerator: "ctrl+s",
click: () => {
this.webContents.send(MainEventType.SAVE);
},
},
{
label: "全部保存",
accelerator: "ctrl+shift+s",
click: () => {
this.webContents.send(MainEventType.SAVE_ALL);
},
},
{
label: "合并导出Json",
click: () => {
(async () => {
const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (res.filePath) {
const curWorkspace = this.settings.curWorkspace;
curWorkspace.writeAllTrees(res.filePath,(err)=>{
const msg = err?err:"导出成功";
dialog.showMessageBox({
type:"info",
buttons:["ok"],
message:msg
})
});
}
})();
},
},
{ type: "separator" },
{
label: "新建节点",
accelerator: "insert",
click: () => {
this.webContents.send(MainEventType.CREATE_NODE, "unknow");
},
},
{
label: "删除节点",
accelerator: "delete",
click: () => {
this.webContents.send(MainEventType.DELETE_NODE);
},
},
{ type: "separator" },
{
label: "复制节点",
accelerator: "ctrl+c",
role: "copy",
click: () => {
this.webContents.send(MainEventType.COPY_NODE);
},
},
{
label: "粘贴节点",
accelerator: "ctrl+v",
click: () => {
this.webContents.send(MainEventType.PASTE_NODE);
},
},
],
});
if (!res.canceled) {
this.settings.curWorkspace.setFilepath(res.filePath);
this.settings.curWorkspace.save();
openWorkspace(res.filePath);
}
})();
}
const recentItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentWorkspaces) {
recentItems.push({
label: path,
click: () => {
console.log("open recent workspace", path);
openWorkspace(path);
}
})
}
return new MenuItem({
label: "工作区",
submenu: [
{
label: "打开",
click: () => {
private createWorkspaceMenu() {
const openWorkspace = (path: string) => {
const curWorkspace = this.settings.curWorkspace;
curWorkspace.setFilepath(path);
curWorkspace.load();
this.settings.pushRecentWorkspace(path);
this.mainProcess.rebuildMenu();
this.webContents.send(
MainEventType.OPEN_DIR,
curWorkspace.getWorkdir(),
curWorkspace.getFilepath()
);
};
const saveToNewPath = () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: "Json", extensions: ['json'] }
]
});
if (res.filePaths.length > 0) {
openWorkspace(res.filePaths[0]);
}
const res = await dialog.showSaveDialog({
properties: ["showOverwriteConfirmation"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (!res.canceled) {
this.settings.curWorkspace.setFilepath(res.filePath);
this.settings.curWorkspace.save();
openWorkspace(res.filePath);
}
})();
}
},
{
label: "保存",
click: () => {
if (this.settings.curWorkspace.getFilepath()) {
this.settings.curWorkspace.save();
};
const recentItems: MenuItemConstructorOptions[] = [];
for (let path of this.settings.recentWorkspaces) {
recentItems.push({
label: path,
click: () => {
console.log("open recent workspace", path);
openWorkspace(path);
},
});
}
return new MenuItem({
label: "工作区",
submenu: [
{
label: "打开",
click: () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (res.filePaths.length > 0) {
openWorkspace(res.filePaths[0]);
}
})();
},
},
{
label: "保存",
click: () => {
if (this.settings.curWorkspace.getFilepath()) {
this.settings.curWorkspace.save();
} else {
saveToNewPath();
}
},
},
{
label: "另存为",
click: () => {
saveToNewPath();
},
},
{
label: "最近打开",
submenu: recentItems,
},
{ type: "separator" },
{
label: "打开目录",
accelerator: "Ctrl+Shift+O",
click: () => {
if (!this.settings.curWorkspace.getNodeConfPath()) {
dialog.showErrorBox("警告", "请先指定节点定义配置!");
return;
}
(async () => {
const res = await dialog.showOpenDialog({
properties: ["openDirectory"],
});
if (res.filePaths.length > 0) {
const curWorkspace = this.settings.curWorkspace;
const path = res.filePaths[0];
curWorkspace.setWorkdir(path);
curWorkspace.save();
this.webContents.send(
MainEventType.OPEN_DIR,
path,
curWorkspace.getFilepath()
);
}
})();
},
},
{
label: "节点定义",
submenu: [
{
label: "选择文件",
click: () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ["openFile"],
filters: [{ name: "Json", extensions: ["json"] }],
});
if (res.filePaths.length > 0) {
const nodeConfigPath = res.filePaths[0];
this.settings.curWorkspace.setNodeConfPath(nodeConfigPath);
this.mainProcess.rebuildMenu();
}
})();
},
},
{ type: "separator" },
{ label: this.settings.nodeConfPath },
],
},
{ type: "separator" },
{
label: "关闭",
click: () => {
app.quit();
},
},
],
});
}
private createToolsMenu() {
return new MenuItem({
label: "开发工具",
submenu: [
{
label: "打开控制台",
accelerator: "Ctrl+Shift+I",
click: (_, browserWindow) => {
browserWindow.webContents.toggleDevTools();
},
},
{
label: "重载",
accelerator: "Ctrl+R",
click: (_, browserWindow) => {
browserWindow.reload();
},
},
],
});
}
private createNodeMenu() {
const classifyItems: MenuItemConstructorOptions[] = [];
const map: { [key: string]: MenuItemConstructorOptions } = {};
for (let t of this.settings.nodeClassify) {
let item: MenuItemConstructorOptions = {
id: t.classify,
label: `${t.classify}(${t.desc})`,
submenu: [],
};
classifyItems.push(item);
map[t.classify] = item;
}
const other: MenuItemConstructorOptions = {
id: "other",
label: "其它",
submenu: [],
};
var hasOther = false;
for (let node of this.settings.nodeConfig) {
const item: MenuItemConstructorOptions = {
label: `${node.name}(${node.desc})`,
click: () => {
console.log("create node", node.name);
this.webContents.send(MainEventType.CREATE_NODE, node.name);
},
};
let typeItem = map[node.type];
if (!typeItem) {
typeItem = other;
hasOther = true;
}
if (typeItem.submenu instanceof Menu) {
console.log("typeItem.submenu error", typeItem);
} else {
saveToNewPath();
typeItem.submenu.push(item);
}
}
},
{
label: '另存为',
click: () => {
saveToNewPath();
},
},
{
label: "最近打开",
submenu: recentItems,
},
{ type: 'separator' },
{
label: "打开目录",
accelerator: "Ctrl+Shift+O",
click: () => {
if (!this.settings.curWorkspace.getNodeConfPath()) {
dialog.showErrorBox("警告", "请先指定节点定义配置!");
return;
}
(async () => {
const res = await dialog.showOpenDialog({
properties: ['openDirectory']
});
if (res.filePaths.length > 0) {
const curWorkspace = this.settings.curWorkspace;
const path = res.filePaths[0];
curWorkspace.setWorkdir(path);
curWorkspace.save();
this.webContents.send(MainEventType.OPEN_DIR, path, curWorkspace.getFilepath());
}
})();
}
},
{
label: "节点定义",
submenu: [
{
label: "选择文件",
click: () => {
(async () => {
const res = await dialog.showOpenDialog({
properties: ['openFile'],
filters: [
{ name: "Json", extensions: ['json'] }
]
});
if (res.filePaths.length > 0) {
const nodeConfigPath = res.filePaths[0];
this.settings.curWorkspace.setNodeConfPath(nodeConfigPath);
this.mainProcess.rebuildMenu();
}
})();
}
},
{ type: 'separator' },
{ label: this.settings.nodeConfPath, }
]
},
{ type: 'separator' },
{
label: "关闭",
click: () => {
app.quit();
}
}
]
});
}
private createToolsMenu() {
return new MenuItem({
label: "开发工具",
submenu: [
{
label: "打开控制台",
accelerator: "Ctrl+Shift+I",
click: (_, browserWindow) => {
browserWindow.webContents.toggleDevTools();
}
},
{
label: "重载",
accelerator: "Ctrl+R",
click: (_, browserWindow) => {
browserWindow.reload();
}
},
]
});
}
private createNodeMenu() {
const classifyItems: MenuItemConstructorOptions[] = [];
const map: { [key: string]: MenuItemConstructorOptions } = {}
for (let t of this.settings.nodeClassify) {
let item: MenuItemConstructorOptions = {
id: t.classify,
label: `${t.classify}(${t.desc})`,
submenu: [],
};
classifyItems.push(item);
map[t.classify] = item;
}
const other: MenuItemConstructorOptions = {
id: 'other',
label: "其它",
submenu: [],
};
var hasOther = false;
for (let node of this.settings.nodeConfig) {
const item: MenuItemConstructorOptions = {
label: `${node.name}(${node.desc})`,
click: () => {
console.log("create node", node.name);
this.webContents.send(MainEventType.CREATE_NODE, node.name);
if (hasOther) {
classifyItems.push(other);
}
}
let typeItem = map[node.type];
if (!typeItem) {
typeItem = other;
hasOther = true;
}
if (typeItem.submenu instanceof Menu) {
console.log("typeItem.submenu error", typeItem);
} else {
typeItem.submenu.push(item);
}
}
if (hasOther) {
classifyItems.push(other);
return new MenuItem({
label: "新建节点",
submenu: classifyItems,
});
}
return new MenuItem({
label: "新建节点",
submenu: classifyItems
});
}
}
}
+117 -86
View File
@@ -1,6 +1,7 @@
import * as fs from 'fs';
import * as path from 'path';
import { BehaviorNodeTypeModel } from '../common/BehaviorTreeModel';
import * as fs from "fs";
import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel";
import * as glob from "glob";
import * as path from "path";
export interface WorkspaceModel {
isRelative?: boolean;
@@ -9,98 +10,128 @@ export interface WorkspaceModel {
}
const unknowNodeType: BehaviorNodeTypeModel = {
name: 'unknow',
desc: '新建节点',
type: 'Action',
}
name: "unknow",
desc: "新建节点",
type: "Action",
};
export default class Workspace {
private filepath: string;
private nodeConfPath: string;
private workdir: string;
private filepath: string;
private nodeConfPath: string;
private workdir: string;
private name2conf: { [name: string]: BehaviorNodeTypeModel } = {};
private types: BehaviorNodeTypeModel[] = [];
private name2conf: { [name: string]: BehaviorNodeTypeModel } = {};
private types: BehaviorNodeTypeModel[] = [];
constructor(filepath: string) {
this.filepath = filepath;
}
load() {
if (!this.filepath) {
return;
}
try {
const str = fs.readFileSync(this.filepath, 'utf8');
const model = JSON.parse(str) as WorkspaceModel;
if (model.isRelative) {
const root = path.dirname(this.filepath);
this.nodeConfPath = root + '/' + model.nodeConfPath;
this.workdir = root + '/' + model.workdir;
} else {
this.nodeConfPath = model.nodeConfPath;
this.workdir = model.workdir;
}
this.initNodeConf();
} catch (error) {
console.log(error);
}
}
private initNodeConf() {
if (!this.nodeConfPath) {
return;
constructor(filepath: string) {
this.filepath = filepath;
}
const types: BehaviorNodeTypeModel[] = JSON.parse(fs.readFileSync(this.nodeConfPath, 'utf8'));
this.name2conf = {};
types.forEach(t => {
this.name2conf[t.name] = t;
})
this.types = types;
}
getNodeConfPath() {
return this.nodeConfPath;
}
setNodeConfPath(path: string) {
this.nodeConfPath = path;
this.initNodeConf();
}
getWorkdir() {
return this.workdir;
}
setWorkdir(workdir: string) {
this.workdir = workdir;
}
save(filepath?: string) {
if (filepath) {
this.filepath = filepath;
load() {
if (!this.filepath) {
return;
}
try {
const str = fs.readFileSync(this.filepath, 'utf8');
const model = JSON.parse(str) as WorkspaceModel;
if (model.isRelative) {
const root = path.dirname(this.filepath);
this.nodeConfPath = root + '/' + model.nodeConfPath;
this.workdir = root + '/' + model.workdir;
} else {
this.nodeConfPath = model.nodeConfPath;
this.workdir = model.workdir;
}
this.initNodeConf();
} catch (error) {
console.log(error);
}
}
if (!this.filepath) {
return;
private initNodeConf() {
if (!this.nodeConfPath) {
return;
}
const types: BehaviorNodeTypeModel[] = JSON.parse(fs.readFileSync(this.nodeConfPath, 'utf8'));
this.name2conf = {};
types.forEach(t => {
this.name2conf[t.name] = t;
})
this.types = types;
}
fs.writeFileSync(this.filepath, JSON.stringify({
nodeConfPath: this.nodeConfPath,
workdir: this.workdir,
}, null, 2))
}
get nodeConf() {
return this.types;
}
getNodeConfPath() {
return this.nodeConfPath;
}
setNodeConfPath(path: string) {
this.nodeConfPath = path;
this.initNodeConf();
}
getWorkdir(): string {
return this.workdir;
}
setWorkdir(workdir: string) {
this.workdir = workdir;
}
getFilepath() {
return this.filepath;
}
writeAllTrees(outFilePath: string, cb?: (err: string) => void) {
if (!fs.existsSync(this.getWorkdir())) {
cb && cb("请先打开工作区");
return;
}
glob.glob(path.join(this.getWorkdir(), "**/*.json"), function (err, files) {
if (err) {
console.error(err);
cb && cb(err.message);
return;
}
setFilepath(filepath: string) {
this.filepath = filepath;
}
let result = new Array();
files.forEach((file) => {
const data = fs.readFileSync(file, "utf8");
result.push(JSON.parse(data));
});
getNodeConf(name: string) {
return this.name2conf[name] || unknowNodeType;
}
}
fs.writeFileSync(outFilePath, JSON.stringify(result, null, 2));
cb && cb(null);
});
}
save(filepath?: string) {
if (filepath) {
this.filepath = filepath;
}
if (!this.filepath) {
return;
}
fs.writeFileSync(
this.filepath,
JSON.stringify(
{
nodeConfPath: this.nodeConfPath,
workdir: this.workdir,
},
null,
2
)
);
}
get nodeConf() {
return this.types;
}
getFilepath() {
return this.filepath;
}
setFilepath(filepath: string) {
this.filepath = filepath;
}
getNodeConf(name: string) {
return this.name2conf[name] || unknowNodeType;
}
}