diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4caff8a --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "endOfLine": "lf", + "semi": true, + "tabWidth": 4, + "printWidth": 100, + "trailingComma": "es5" +} diff --git a/main-process/AppMenu.ts b/main-process/AppMenu.ts index e87975a..10315de 100644 --- a/main-process/AppMenu.ts +++ b/main-process/AppMenu.ts @@ -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 - }); - } -} \ No newline at end of file +} diff --git a/main-process/Workspace.ts b/main-process/Workspace.ts index 1404dad..6c08822 100644 --- a/main-process/Workspace.ts +++ b/main-process/Workspace.ts @@ -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; - } -} \ No newline at end of file + 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; + } +}