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
+1484 -1518

No files matched your search

+28 -28
View File
@@ -1,44 +1,44 @@
import { TreeGraphData } from "@antv/g6/lib/types";
export interface ArgsDefType {
name: string,
type: string,
desc: string,
name: string;
type: string;
desc: string;
}
export interface BehaviorNodeTypeModel {
name: string;
type?: string;
desc?: string;
args?: ArgsDefType[];
input?: string[];
output?: string[];
doc?: string;
name: string;
type?: string;
desc?: string;
args?: ArgsDefType[];
input?: string[];
output?: string[];
doc?: string;
}
export interface BehaviorNodeModel {
id: number;
name: string;
desc?: string;
args?: {[key: string]: any};
input?: string[];
output?: string[];
children?: BehaviorNodeModel[];
debug?: boolean;
id: number;
name: string;
desc?: string;
args?: { [key: string]: any };
input?: string[];
output?: string[];
children?: BehaviorNodeModel[];
debug?: boolean;
}
export interface BehaviorTreeModel {
name: string;
root: BehaviorNodeModel;
name: string;
root: BehaviorNodeModel;
}
export interface GraphNodeModel extends TreeGraphData {
name: string;
desc?: string;
args?: {[key: string]: any};
input?: string[];
output?: string[];
children?: GraphNodeModel[];
conf: BehaviorNodeTypeModel;
name: string;
desc?: string;
args?: { [key: string]: any };
input?: string[];
output?: string[];
children?: GraphNodeModel[];
conf: BehaviorNodeTypeModel;
size?: number[];
size?: number[];
}
+11 -11
View File
@@ -1,12 +1,12 @@
export default {
OPEN_FILE: "OPEN_FILE",
OPEN_DIR: "OPEN_DIR",
OPEN_WORKSPACE: "OPEN_WORKSPACE",
CREATE_TREE: 'CREATE_TREE',
CREATE_NODE: 'CREATE_NODE',
DELETE_NODE: 'DELETE_NODE',
COPY_NODE: 'COPY_NODE',
PASTE_NODE: 'PASTE_NODE',
SAVE: 'SAVE',
SAVE_ALL: 'SAVE_ALL',
}
OPEN_FILE: "OPEN_FILE",
OPEN_DIR: "OPEN_DIR",
OPEN_WORKSPACE: "OPEN_WORKSPACE",
CREATE_TREE: "CREATE_TREE",
CREATE_NODE: "CREATE_NODE",
DELETE_NODE: "DELETE_NODE",
COPY_NODE: "COPY_NODE",
PASTE_NODE: "PASTE_NODE",
SAVE: "SAVE",
SAVE_ALL: "SAVE_ALL",
};
+104 -105
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 Settings from "../main-process/Settings";
import { remote } from "electron";
import * as path from 'path';
import * as path from "path";
export const cloneNodeData = (nodeData: GraphNodeModel) => {
const newData: GraphNodeModel = {
id: nodeData.id + "new",
name: nodeData.name,
conf: nodeData.conf,
}
return newData;
}
const newData: GraphNodeModel = {
id: nodeData.id + "new",
name: nodeData.name,
conf: nodeData.conf,
};
return newData;
};
export const refreshNodeId = (nodeData: GraphNodeModel, id?: number) => {
if (!id) {
id = 1;
}
nodeData.id = (id++).toString();
if (nodeData.children) {
nodeData.children.forEach(child => {
id = refreshNodeId(child, id);
})
}
return id;
}
if (!id) {
id = 1;
}
nodeData.id = (id++).toString();
if (nodeData.children) {
nodeData.children.forEach((child) => {
id = refreshNodeId(child, id);
});
}
return id;
};
export const calcTreeNodeSize = (treeNode: GraphNodeModel) => {
var height = 40;
const updateHeight = (obj: any) => {
if (Array.isArray(obj) || (obj && Object.keys(obj).length > 0)) {
height += 25;
}
}
updateHeight(treeNode.conf.args);
updateHeight(treeNode.conf.input);
updateHeight(treeNode.conf.output);
return [200, height];
}
var height = 40;
const updateHeight = (obj: any) => {
if (Array.isArray(obj) || (obj && Object.keys(obj).length > 0)) {
height += 25;
}
};
updateHeight(treeNode.conf.args);
updateHeight(treeNode.conf.input);
updateHeight(treeNode.conf.output);
return [200, height];
};
export const createTreeData = (bNode: BehaviorNodeModel, settings: Settings) => {
const treeData: GraphNodeModel = {
id: bNode.id.toString(),
name: bNode.name,
desc: bNode.desc,
args: bNode.args,
input: bNode.input,
output: bNode.output,
conf: settings.getNodeConf(bNode.name),
}
treeData.size = calcTreeNodeSize(treeData);
if (bNode.children) {
treeData.children = [];
bNode.children.forEach(child => {
treeData.children.push(createTreeData(child, settings));
})
}
calcTreeNodeSize(treeData);
return treeData;
}
const treeData: GraphNodeModel = {
id: bNode.id.toString(),
name: bNode.name,
desc: bNode.desc,
args: bNode.args,
input: bNode.input,
output: bNode.output,
conf: settings.getNodeConf(bNode.name),
};
treeData.size = calcTreeNodeSize(treeData);
if (bNode.children) {
treeData.children = [];
bNode.children.forEach((child) => {
treeData.children.push(createTreeData(child, settings));
});
}
calcTreeNodeSize(treeData);
return treeData;
};
export const createFileData = (gNode: GraphNodeModel) => {
const nodeData: BehaviorNodeModel = {
id: Number(gNode.id),
name: gNode.name,
desc: gNode.desc || undefined,
args: gNode.args || undefined,
input: gNode.input || undefined,
output: gNode.output || undefined,
}
if (gNode.children) {
nodeData.children = [];
gNode.children.forEach(child => {
nodeData.children.push(createFileData(child));
})
}
return nodeData;
}
const nodeData: BehaviorNodeModel = {
id: Number(gNode.id),
name: gNode.name,
desc: gNode.desc || undefined,
args: gNode.args || undefined,
input: gNode.input || undefined,
output: gNode.output || undefined,
};
if (gNode.children) {
nodeData.children = [];
gNode.children.forEach((child) => {
nodeData.children.push(createFileData(child));
});
}
return nodeData;
};
export const findParent = (node: TreeGraphData, id: string): TreeGraphData | null => {
if (node.children) {
for (let child of node.children) {
if (child.id == id) {
return node;
} else {
let parent = findParent(child, id);
if (parent) {
return parent;
if (node.children) {
for (let child of node.children) {
if (child.id == id) {
return node;
} else {
let parent = findParent(child, id);
if (parent) {
return parent;
}
}
}
}
}
}
return null;
}
return null;
};
export const findFromAllChildren = (node: TreeGraphData, id: string): TreeGraphData | null => {
if (node.id == id) {
return node;
}
if (node.children) {
for (let child of node.children) {
let found = findFromAllChildren(child, id);
if (found) {
return found;
}
if (node.id == id) {
return node;
}
}
return null;
}
if (node.children) {
for (let child of node.children) {
let found = findFromAllChildren(child, id);
if (found) {
return found;
}
}
}
return null;
};
export const getRemoteSettings = () => {
return remote.getGlobal("settings") as Settings;
}
return remote.getGlobal("settings") as Settings;
};
export const fileName2treeName = (filename: string) => {
return path.basename(filename).slice(0, -5);
}
return path.basename(filename).slice(0, -5);
};
export const createNewTree = (filename: string) => {
const tree: BehaviorTreeModel = {
name: fileName2treeName(filename),
root: {
id: 1,
name: 'Sequence',
desc: '新建行为树'
}
}
return tree;
}
const tree: BehaviorTreeModel = {
name: fileName2treeName(filename),
root: {
id: 1,
name: "Sequence",
desc: "新建行为树",
},
};
return tree;
};
+6 -6
View File
@@ -121,13 +121,13 @@ export default class AppMenu {
});
if (res.filePath) {
const curWorkspace = this.settings.curWorkspace;
curWorkspace.writeAllTrees(res.filePath,(err)=>{
const msg = err?err:"导出成功";
curWorkspace.writeAllTrees(res.filePath, (err) => {
const msg = err ? err : "导出成功";
dialog.showMessageBox({
type:"info",
buttons:["ok"],
message:msg
})
type: "info",
buttons: ["ok"],
message: msg,
});
});
}
})();
+56 -58
View File
@@ -1,72 +1,70 @@
import { app, BrowserWindow, Menu, MenuItem, dialog, nativeTheme } from 'electron';
import AppMenu from './AppMenu'
import Settings from './Settings';
import MainEventType from '../common/MainEventType';
import { app, BrowserWindow, Menu, MenuItem, dialog, nativeTheme } from "electron";
import AppMenu from "./AppMenu";
import Settings from "./Settings";
import MainEventType from "../common/MainEventType";
const electronLocalshortcut = require('electron-localshortcut');
const electronLocalshortcut = require("electron-localshortcut");
// 一些暴露给render-process的全局变量
export interface Global {
settings: Settings
settings: Settings;
}
declare var global: Global;
export class MainProcess {
mainWindow: BrowserWindow;
appMenu: AppMenu;
settings: Settings;
constructor() {
nativeTheme.themeSource = 'dark';
app.on('ready', () => {
this.createWindow();
electronLocalshortcut.register(this.mainWindow, 'Ctrl+C', () => {
this.mainWindow.webContents.send(MainEventType.COPY_NODE);
});
electronLocalshortcut.register(this.mainWindow, 'Ctrl+V', () => {
this.mainWindow.webContents.send(MainEventType.PASTE_NODE);
});
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (this.mainWindow === null) {
this.createWindow()
}
});
}
mainWindow: BrowserWindow;
appMenu: AppMenu;
settings: Settings;
constructor() {
nativeTheme.themeSource = "dark";
app.on("ready", () => {
this.createWindow();
electronLocalshortcut.register(this.mainWindow, "Ctrl+C", () => {
this.mainWindow.webContents.send(MainEventType.COPY_NODE);
});
electronLocalshortcut.register(this.mainWindow, "Ctrl+V", () => {
this.mainWindow.webContents.send(MainEventType.PASTE_NODE);
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (this.mainWindow === null) {
this.createWindow();
}
});
}
createWindow() {
this.settings = new Settings();
global.settings = this.settings;
createWindow() {
this.settings = new Settings();
global.settings = this.settings;
this.mainWindow = new BrowserWindow({
width: 1280,
height: 800,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
}
// fullscreenable:false,
// maximizable:false
});
this.mainWindow.maximize();
// mainWindow.webContents.openDevTools();
this.mainWindow.loadFile('index.html');
this.mainWindow.on('closed', function () {
this.mainWindow = null
});
this.appMenu = new AppMenu(this);
this.rebuildMenu();
}
this.mainWindow = new BrowserWindow({
width: 1280,
height: 800,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
},
// fullscreenable:false,
// maximizable:false
});
this.mainWindow.maximize();
// mainWindow.webContents.openDevTools();
this.mainWindow.loadFile("index.html");
this.mainWindow.on("closed", function () {
this.mainWindow = null;
});
this.appMenu = new AppMenu(this);
this.rebuildMenu();
}
rebuildMenu() {
Menu.setApplicationMenu(this.appMenu.createMenu());
}
rebuildMenu() {
Menu.setApplicationMenu(this.appMenu.createMenu());
}
}
export default new MainProcess();
+74 -75
View File
@@ -1,90 +1,89 @@
import * as fs from 'fs';
import { BehaviorNodeTypeModel } from '../common/BehaviorTreeModel';
import Workspace from './Workspace';
import * as fs from "fs";
import { BehaviorNodeTypeModel } from "../common/BehaviorTreeModel";
import Workspace from "./Workspace";
export interface BehaviorNodeClassify {
classify: string;
desc?: string;
classify: string;
desc?: string;
}
export interface SettingsModel {
recentWorkspaces?: string[];
recentFiles?: string[];
nodeClassify?: BehaviorNodeClassify[];
recentWorkspaces?: string[];
recentFiles?: string[];
nodeClassify?: BehaviorNodeClassify[];
}
const settingPath = 'settings.json';
const settingPath = "settings.json";
const sampleNodeClassify: BehaviorNodeClassify[] = [
{ classify: "Composite", desc: "复合节点" },
{ classify: "Decorator", desc: "修饰节点" },
{ classify: "Condition", desc: "条件节点" },
{ classify: "Action", desc: "行为节点" },
]
{ classify: "Composite", desc: "复合节点" },
{ classify: "Decorator", desc: "修饰节点" },
{ classify: "Condition", desc: "条件节点" },
{ classify: "Action", desc: "行为节点" },
];
export default class Settings {
private settings: SettingsModel;
curWorkspace?: Workspace;
private settings: SettingsModel;
curWorkspace?: Workspace;
constructor() {
this.load();
}
get nodeConfPath() {
return this.curWorkspace.getNodeConfPath();
}
get recentWorkspaces() {
return this.settings.recentWorkspaces;
}
get recentFiles() {
return this.settings.recentFiles;
}
get nodeConfig() {
return this.curWorkspace.nodeConf;
}
get nodeClassify() {
return this.settings.nodeClassify;
}
set(config: SettingsModel) {
this.settings = {
...this.settings,
...config
};
this.save();
}
load() {
if (fs.existsSync(settingPath)) {
const str = fs.readFileSync(settingPath, 'utf8');
this.settings = JSON.parse(str);
} else {
this.settings = {
recentWorkspaces: ['sample/workspace.json'],
recentFiles: [],
nodeClassify: sampleNodeClassify,
};
this.save();
constructor() {
this.load();
}
this.curWorkspace = new Workspace(this.settings.recentWorkspaces[0]);
this.curWorkspace.load();
}
save() {
fs.writeFileSync(settingPath, JSON.stringify(this.settings, null, 2));
}
getNodeConf(name: string) {
return this.curWorkspace.getNodeConf(name);
}
pushRecentWorkspace(path: string) {
if (this.settings.recentWorkspaces.indexOf(path) < 0) {
this.settings.recentWorkspaces.unshift(path);
console.log("push", path);
} else {
console.log("already in path", this.settings.recentWorkspaces, path);
get nodeConfPath() {
return this.curWorkspace.getNodeConfPath();
}
get recentWorkspaces() {
return this.settings.recentWorkspaces;
}
get recentFiles() {
return this.settings.recentFiles;
}
get nodeConfig() {
return this.curWorkspace.nodeConf;
}
get nodeClassify() {
return this.settings.nodeClassify;
}
set(config: SettingsModel) {
this.settings = {
...this.settings,
...config,
};
this.save();
}
load() {
if (fs.existsSync(settingPath)) {
const str = fs.readFileSync(settingPath, "utf8");
this.settings = JSON.parse(str);
} else {
this.settings = {
recentWorkspaces: ["sample/workspace.json"],
recentFiles: [],
nodeClassify: sampleNodeClassify,
};
this.save();
}
this.curWorkspace = new Workspace(this.settings.recentWorkspaces[0]);
this.curWorkspace.load();
}
save() {
fs.writeFileSync(settingPath, JSON.stringify(this.settings, null, 2));
}
getNodeConf(name: string) {
return this.curWorkspace.getNodeConf(name);
}
pushRecentWorkspace(path: string) {
if (this.settings.recentWorkspaces.indexOf(path) < 0) {
this.settings.recentWorkspaces.unshift(path);
console.log("push", path);
} else {
console.log("already in path", this.settings.recentWorkspaces, path);
}
this.save();
}
this.save();
}
}
+17 -15
View File
@@ -4,9 +4,9 @@ import * as glob from "glob";
import * as path from "path";
export interface WorkspaceModel {
isRelative?: boolean;
nodeConfPath: string;
workdir: string;
isRelative?: boolean;
nodeConfPath: string;
workdir: string;
}
const unknowNodeType: BehaviorNodeTypeModel = {
@@ -32,15 +32,15 @@ export default class Workspace {
return;
}
try {
const str = fs.readFileSync(this.filepath, 'utf8');
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;
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.nodeConfPath = model.nodeConfPath;
this.workdir = model.workdir;
}
this.initNodeConf();
@@ -52,14 +52,16 @@ export default class Workspace {
private initNodeConf() {
if (!this.nodeConfPath) {
return;
}
}
const types: BehaviorNodeTypeModel[] = JSON.parse(fs.readFileSync(this.nodeConfPath, 'utf8'));
this.name2conf = {};
types.forEach(t => {
const types: BehaviorNodeTypeModel[] = JSON.parse(
fs.readFileSync(this.nodeConfPath, "utf8")
);
this.name2conf = {};
types.forEach((t) => {
this.name2conf[t.name] = t;
})
this.types = types;
});
this.types = types;
}
getNodeConfPath() {
+267 -273
View File
@@ -1,297 +1,291 @@
import React from 'react';
import { Card, Divider, Form, Input, 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';
import React from "react";
import {
Card,
Divider,
Form,
Input,
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;
interface NodePanelProps {
model: BehaviorNodeModel;
settings: Settings;
updateNode: (id: string, forceUpdate:boolean) => void;
model: BehaviorNodeModel;
settings: Settings;
updateNode: (id: string, forceUpdate: boolean) => void;
}
interface NodePanelState {
}
interface NodePanelState {}
export default class NodePanel extends React.Component<NodePanelProps> {
formRef = React.createRef<FormInstance>();
formRef = React.createRef<FormInstance>();
componentDidUpdate() {
this.formRef.current.resetFields();
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;
}
componentDidUpdate() {
this.formRef.current.resetFields();
this.formRef.current.setFieldsValue(this.getInitialValues());
}
if (conf.args) {
conf.args.forEach(e => {
const k = 'args.' + e.name;
if (e.type.indexOf('number') > 0) {
args[e.name] = Number(values[k]);
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) {
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 {
args[e.name] = values[k];
model.input = null;
}
})
}
model.args = args;
this.formRef.current.setFieldsValue({ customArgs: model.args ? JSON.stringify(model.args, null, " ") : '' });
if (conf.output) {
model.output = [];
conf.output.forEach((e, i) => {
model.output.push(values["output." + i] || "");
});
} else {
model.output = null;
}
if (conf.input) {
model.input = [];
conf.input.forEach((e, i) => {
model.input.push(values['input.' + i] || '');
})
} 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 },
if (forceUpdate) {
this.forceUpdate();
}
updateNode(model.id.toString(), forceUpdate);
};
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) {
if (!conf || !conf.args || conf.args.length == 0) {
return null;
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>
);
}
// 普通参数
const normalArgs = (e: ArgsDefType) => {
const required = e.type.indexOf("?") == -1;
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>
)
})
renderArgs(conf: BehaviorNodeTypeModel) {
if (!conf || !conf.args || conf.args.length == 0) {
return null;
}
</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) => {
// 普通参数
const normalArgs = (e: ArgsDefType) => {
const required = e.type.indexOf("?") == -1;
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
label={e}
name={`output.${i}`}
key={`output.${i}`}
>
<Input onBlur={this.handleSubmit} />
</Item>
)
})
}
</div>
)
}
<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) {
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>
);
}
}
+18 -23
View File
@@ -1,33 +1,28 @@
import React from 'react';
import { Card } from 'antd';
import { INode } from '@antv/g6/lib/interface/item';
import { BehaviorTreeModel } from '../../common/BehaviorTreeModel';
import React from "react";
import { Card } from "antd";
import { INode } from "@antv/g6/lib/interface/item";
import { BehaviorTreeModel } from "../../common/BehaviorTreeModel";
interface TreePanelProps {
model?: BehaviorTreeModel;
model?: BehaviorTreeModel;
}
interface TreePanelState {
}
interface TreePanelState {}
export default class TreePanel extends React.Component<TreePanelProps, TreePanelState> {
state: TreePanelState = {};
state: TreePanelState = {};
componentDidMount() {
}
componentDidMount() {}
render() {
const { model } = this.props;
if(!model) {
return (<div/>)
render() {
const { model } = this.props;
if (!model) {
return <div />;
}
return (
<Card title="概况" style={{ height: "100%" }}>
行为树:{model.name}
</Card>
);
}
return (
<Card title="概况" style={{ height: "100%" }}>
行为树:{model.name}
</Card>
)
}
}
+398 -396
View File
@@ -1,412 +1,414 @@
import * as React from 'react';
import * as fs from 'fs';
import * as path from 'path';
import { Row, Col, message } from 'antd';
import NodePanel from './NodePanel';
import G6, { TreeGraph } from '@antv/g6';
import { G6GraphEvent } from '@antv/g6/lib/interface/behavior';
import * as Utils from '../../common/Utils';
import { BehaviorTreeModel, GraphNodeModel, BehaviorNodeModel } from '../../common/BehaviorTreeModel';
import TreePanel from './TreePanel';
import Settings from '../../main-process/Settings';
import * as React from "react";
import * as fs from "fs";
import * as path from "path";
import { Row, Col, message } from "antd";
import NodePanel from "./NodePanel";
import G6, { TreeGraph } from "@antv/g6";
import { G6GraphEvent } from "@antv/g6/lib/interface/behavior";
import * as Utils from "../../common/Utils";
import {
BehaviorTreeModel,
GraphNodeModel,
BehaviorNodeModel,
} from "../../common/BehaviorTreeModel";
import TreePanel from "./TreePanel";
import Settings from "../../main-process/Settings";
import './Editor.css';
import { clipboard } from 'electron';
import "./Editor.css";
import { clipboard } from "electron";
export interface EditorProps {
filepath: string;
filepath: string;
}
interface EditorState {
curNodeId?: string;
treeModel?: BehaviorTreeModel;
settings?: Settings;
curNodeId?: string;
treeModel?: BehaviorTreeModel;
settings?: Settings;
}
export default class Editor extends React.Component<EditorProps, EditorState> {
private ref: React.RefObject<any>;
state: EditorState = {};
private ref: React.RefObject<any>;
state: EditorState = {};
private graph: TreeGraph;
private dragSrcId: string;
private dragDstId: string;
private autoId: number;
private graph: TreeGraph;
private dragSrcId: string;
private dragDstId: string;
private autoId: number;
constructor(props: EditorProps) {
super(props);
this.ref = React.createRef();
}
constructor(props: EditorProps) {
super(props);
this.ref = React.createRef();
}
componentDidMount() {
const graph = new TreeGraph({
container: this.ref.current,
width: window.screen.width * 0.66,
height: window.screen.height,
animate: false,
// fitCenter: true,
modes: {
default: [
'drag-canvas',
'zoom-canvas',
'click-select',
'hover',
{
type: 'collapse-expand',
trigger: 'dblclick',
onChange: (item, collapsed) => {
this.onSelectNode(item.getID());
const data = item.getModel();
data.collapsed = collapsed;
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');
marker.attr('symbol', icon);
return true;
componentDidMount() {
const graph = new TreeGraph({
container: this.ref.current,
width: window.screen.width * 0.66,
height: window.screen.height,
animate: false,
// fitCenter: true,
modes: {
default: [
"drag-canvas",
"zoom-canvas",
"click-select",
"hover",
{
type: "collapse-expand",
trigger: "dblclick",
onChange: (item, collapsed) => {
this.onSelectNode(item.getID());
const data = item.getModel();
data.collapsed = collapsed;
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");
marker.attr("symbol", icon);
return true;
},
},
],
},
},
]
},
defaultEdge: {
type: 'cubic-horizontal',
style: {
stroke: '#A3B1BF'
}
},
defaultNode: {
type: "TreeNode",
},
layout: {
type: 'compactBox',
direction: 'LR',
getHGap: () => 50,
getWidth: (d: GraphNodeModel) => {
return 150;
},
getHeight: (d: GraphNodeModel) => {
if (d.size) {
return d.size[1];
} else {
return 50;
}
}
}
});
graph.on('node:mouseenter', (e: G6GraphEvent) => {
const { item } = e;
if (item.hasState('selected')) {
return;
}
graph.setItemState(item, 'hover', true);
});
graph.on('node:mouseleave', (e: G6GraphEvent) => {
const { item } = e;
if (item.hasState('selected')) {
return;
}
graph.setItemState(item, 'hover', false);
});
graph.on('nodeselectchange', (e: G6GraphEvent) => {
if (e.target) {
this.onSelectNode(e.target.getID());
} else {
this.onSelectNode(null);
}
});
const clearDragDstState = () => {
if (this.dragDstId) {
graph.setItemState(this.dragDstId, 'dragRight', false);
graph.setItemState(this.dragDstId, 'dragDown', false);
graph.setItemState(this.dragDstId, 'dragUp', false);
this.dragDstId = null;
}
}
const clearDragSrcState = () => {
if (this.dragSrcId) {
graph.setItemState(this.dragSrcId, 'dragSrc', false);
this.dragSrcId = null;
}
}
graph.on('node:dragstart', (e: G6GraphEvent) => {
this.dragSrcId = e.item.getID();
graph.setItemState(this.dragSrcId, 'dragSrc', true);
});
graph.on('node:dragend', (e: G6GraphEvent) => {
if (this.dragSrcId) {
graph.setItemState(this.dragSrcId, 'dragSrc', false);
this.dragSrcId = null;
}
});
graph.on('node:dragover', (e: G6GraphEvent) => {
const dstNodeId = e.item.getID();
if (dstNodeId == this.dragSrcId) {
return;
}
if (this.dragDstId) {
graph.setItemState(this.dragDstId, 'dragRight', false);
graph.setItemState(this.dragDstId, 'dragDown', false);
graph.setItemState(this.dragDstId, 'dragUp', false);
}
const box = e.item.getBBox();
if (e.x > box.minX + box.width * 0.6) {
graph.setItemState(dstNodeId, 'dragRight', true);
} else if (e.y > box.minY + box.height * 0.5) {
graph.setItemState(dstNodeId, 'dragDown', true);
} else {
graph.setItemState(dstNodeId, 'dragUp', true);
}
this.dragDstId = dstNodeId;
});
graph.on('node:dragleave', (e: G6GraphEvent) => {
clearDragDstState();
})
graph.on('node:drop', (e: G6GraphEvent) => {
const srcNodeId = this.dragSrcId;
const dstNode = e.item;
var dragDir;
if (dstNode.hasState('dragRight')) {
dragDir = 'dragRight';
} else if (dstNode.hasState('dragDown')) {
dragDir = 'dragDown';
} else if (dstNode.hasState('dragUp')) {
dragDir = 'dragUp';
}
clearDragSrcState();
clearDragDstState();
if (!srcNodeId) {
console.log("no drag src");
return;
}
if (srcNodeId == dstNode.getID()) {
console.log("drop same node");
return;
}
const rootData = graph.findDataById('1');
const srcData = graph.findDataById(srcNodeId);
const srcParent = Utils.findParent(rootData, srcNodeId);
const dstData = graph.findDataById(dstNode.getID());
const dstParent = Utils.findParent(rootData, dstNode.getID());
if (!srcParent) {
console.log("no parent!");
return;
}
if (Utils.findFromAllChildren(srcData, dstData.id)) {
// 不能将父节点拖到自已的子孙节点
console.log("cannot move to child");
return;
}
const removeSrc = () => {
srcParent.children = srcParent.children.filter(e => e.id != srcData.id);
}
console.log("dstNode", dstNode);
if (dragDir == 'dragRight') {
removeSrc();
if (!dstData.children) {
dstData.children = [];
}
dstData.children.push(srcData);
} else if (dragDir == 'dragUp') {
if (!dstParent) {
return;
}
removeSrc();
const idx = dstParent.children.findIndex(e => e.id == dstData.id);
dstParent.children.splice(idx, 0, srcData);
} else if (dragDir == 'dragDown') {
if (!dstParent) {
return;
}
removeSrc();
const idx = dstParent.children.findIndex(e => e.id == dstData.id);
dstParent.children.splice(idx + 1, 0, srcData);
} else {
return;
}
// console.log("cur data", graph.findDataById('1'));
this.changeWithoutAnim();
});
const settings = Utils.getRemoteSettings();
const str = fs.readFileSync(this.props.filepath, 'utf8');
let tree: BehaviorTreeModel = JSON.parse(str);
const data = Utils.createTreeData(tree.root, settings);
this.autoId = Utils.refreshNodeId(data);
graph.data(data);
graph.render();
graph.fitCenter();
graph.set('animate', true);
this.graph = graph;
this.setState({ treeModel: tree, settings });
}
onSelectNode(curNodeId: string | null) {
const graph = this.graph;
if (this.state.curNodeId) {
graph.setItemState(this.state.curNodeId, 'selected', false);
}
this.setState({ curNodeId });
if (this.state.curNodeId) {
graph.setItemState(this.state.curNodeId, 'selected', true);
}
}
createNode(name: string) {
console.log("editor create node", name);
const { curNodeId, settings } = this.state;
if (!curNodeId) {
message.warn("未选中节点");
return;
}
const curNodeData = this.graph.findDataById(curNodeId);
const newNodeData: BehaviorNodeModel = {
id: this.autoId++,
name: name,
}
if (!curNodeData.children) {
curNodeData.children = [];
}
curNodeData.children.push(Utils.createTreeData(newNodeData, settings));
this.changeWithoutAnim();
}
deleteNode() {
console.log("editor delete node");
const { curNodeId } = this.state;
if (!curNodeId) {
return;
}
if(curNodeId == '1') {
message.warn("根节点不能删除!");
return;
}
this.onSelectNode(null);
const rootData = this.graph.findDataById('1');
const parentData = Utils.findParent(rootData, curNodeId);
parentData.children = parentData.children.filter(e => e.id != curNodeId);
this.changeWithoutAnim();
}
updateNode(id: string) {
this.graph.changeData();
this.graph.layout();
}
changeWithoutAnim() {
this.graph.set('animate', false);
this.graph.changeData();
this.graph.layout();
this.graph.set('animate', true);
}
save() {
const { filepath } = this.props;
const root = Utils.createFileData(this.graph.findDataById('1') as GraphNodeModel);
fs.writeFileSync(filepath, JSON.stringify({
name: path.basename(filepath).slice(0, -5),
root,
desc: root.desc,
}));
}
copyNode() {
console.log("editor copy node");
const { curNodeId } = this.state;
if (!curNodeId) {
return;
}
const data = this.graph.findDataById(curNodeId);
clipboard.writeText(JSON.stringify(data, null, 2));
}
pasteNode() {
const { curNodeId } = this.state;
if (!curNodeId) {
message.warn("未选中节点");
return;
}
const curNodeData = this.graph.findDataById(curNodeId);
try {
const str = clipboard.readText();
if(!str || str == '') {
return;
}
const data = JSON.parse(str);
if(!curNodeData.children) {
curNodeData.children = [];
}
curNodeData.children.push(data);
this.autoId = Utils.refreshNodeId(this.graph.findDataById('1') as GraphNodeModel);
this.changeWithoutAnim();
} catch (error) {
message.error("粘贴数据有误");
}
}
render() {
const { curNodeId, treeModel, settings } = this.state;
var curNode: any;
if (curNodeId) {
curNode = this.graph.findDataById(curNodeId);
}
return (
<div className="editor">
<Row className="editorBd">
<Col
span={18}
className="editorContent"
ref={this.ref}
/>
<Col span={6} className="editorSidebar">
{
curNode ?
<NodePanel
model={curNode}
settings={settings}
updateNode={(id, forceUpdate) => {
if (forceUpdate) {
const data: any = this.graph.findDataById(id);
data.conf = settings.getNodeConf(data.name);
data.size = Utils.calcTreeNodeSize(data);
this.changeWithoutAnim();
defaultEdge: {
type: "cubic-horizontal",
style: {
stroke: "#A3B1BF",
},
},
defaultNode: {
type: "TreeNode",
},
layout: {
type: "compactBox",
direction: "LR",
getHGap: () => 50,
getWidth: (d: GraphNodeModel) => {
return 150;
},
getHeight: (d: GraphNodeModel) => {
if (d.size) {
return d.size[1];
} else {
return 50;
}
const item = this.graph.findById(id);
item.draw();
}}
/>
:
<TreePanel
model={treeModel}
/>
},
},
});
graph.on("node:mouseenter", (e: G6GraphEvent) => {
const { item } = e;
if (item.hasState("selected")) {
return;
}
</Col>
</Row>
</div>
);
}
graph.setItemState(item, "hover", true);
});
graph.on("node:mouseleave", (e: G6GraphEvent) => {
const { item } = e;
if (item.hasState("selected")) {
return;
}
graph.setItemState(item, "hover", false);
});
graph.on("nodeselectchange", (e: G6GraphEvent) => {
if (e.target) {
this.onSelectNode(e.target.getID());
} else {
this.onSelectNode(null);
}
});
const clearDragDstState = () => {
if (this.dragDstId) {
graph.setItemState(this.dragDstId, "dragRight", false);
graph.setItemState(this.dragDstId, "dragDown", false);
graph.setItemState(this.dragDstId, "dragUp", false);
this.dragDstId = null;
}
};
const clearDragSrcState = () => {
if (this.dragSrcId) {
graph.setItemState(this.dragSrcId, "dragSrc", false);
this.dragSrcId = null;
}
};
graph.on("node:dragstart", (e: G6GraphEvent) => {
this.dragSrcId = e.item.getID();
graph.setItemState(this.dragSrcId, "dragSrc", true);
});
graph.on("node:dragend", (e: G6GraphEvent) => {
if (this.dragSrcId) {
graph.setItemState(this.dragSrcId, "dragSrc", false);
this.dragSrcId = null;
}
});
graph.on("node:dragover", (e: G6GraphEvent) => {
const dstNodeId = e.item.getID();
if (dstNodeId == this.dragSrcId) {
return;
}
if (this.dragDstId) {
graph.setItemState(this.dragDstId, "dragRight", false);
graph.setItemState(this.dragDstId, "dragDown", false);
graph.setItemState(this.dragDstId, "dragUp", false);
}
const box = e.item.getBBox();
if (e.x > box.minX + box.width * 0.6) {
graph.setItemState(dstNodeId, "dragRight", true);
} else if (e.y > box.minY + box.height * 0.5) {
graph.setItemState(dstNodeId, "dragDown", true);
} else {
graph.setItemState(dstNodeId, "dragUp", true);
}
this.dragDstId = dstNodeId;
});
graph.on("node:dragleave", (e: G6GraphEvent) => {
clearDragDstState();
});
graph.on("node:drop", (e: G6GraphEvent) => {
const srcNodeId = this.dragSrcId;
const dstNode = e.item;
var dragDir;
if (dstNode.hasState("dragRight")) {
dragDir = "dragRight";
} else if (dstNode.hasState("dragDown")) {
dragDir = "dragDown";
} else if (dstNode.hasState("dragUp")) {
dragDir = "dragUp";
}
clearDragSrcState();
clearDragDstState();
if (!srcNodeId) {
console.log("no drag src");
return;
}
if (srcNodeId == dstNode.getID()) {
console.log("drop same node");
return;
}
const rootData = graph.findDataById("1");
const srcData = graph.findDataById(srcNodeId);
const srcParent = Utils.findParent(rootData, srcNodeId);
const dstData = graph.findDataById(dstNode.getID());
const dstParent = Utils.findParent(rootData, dstNode.getID());
if (!srcParent) {
console.log("no parent!");
return;
}
if (Utils.findFromAllChildren(srcData, dstData.id)) {
// 不能将父节点拖到自已的子孙节点
console.log("cannot move to child");
return;
}
const removeSrc = () => {
srcParent.children = srcParent.children.filter((e) => e.id != srcData.id);
};
console.log("dstNode", dstNode);
if (dragDir == "dragRight") {
removeSrc();
if (!dstData.children) {
dstData.children = [];
}
dstData.children.push(srcData);
} else if (dragDir == "dragUp") {
if (!dstParent) {
return;
}
removeSrc();
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
dstParent.children.splice(idx, 0, srcData);
} else if (dragDir == "dragDown") {
if (!dstParent) {
return;
}
removeSrc();
const idx = dstParent.children.findIndex((e) => e.id == dstData.id);
dstParent.children.splice(idx + 1, 0, srcData);
} else {
return;
}
// console.log("cur data", graph.findDataById('1'));
this.changeWithoutAnim();
});
const settings = Utils.getRemoteSettings();
const str = fs.readFileSync(this.props.filepath, "utf8");
let tree: BehaviorTreeModel = JSON.parse(str);
const data = Utils.createTreeData(tree.root, settings);
this.autoId = Utils.refreshNodeId(data);
graph.data(data);
graph.render();
graph.fitCenter();
graph.set("animate", true);
this.graph = graph;
this.setState({ treeModel: tree, settings });
}
onSelectNode(curNodeId: string | null) {
const graph = this.graph;
if (this.state.curNodeId) {
graph.setItemState(this.state.curNodeId, "selected", false);
}
this.setState({ curNodeId });
if (this.state.curNodeId) {
graph.setItemState(this.state.curNodeId, "selected", true);
}
}
createNode(name: string) {
console.log("editor create node", name);
const { curNodeId, settings } = this.state;
if (!curNodeId) {
message.warn("未选中节点");
return;
}
const curNodeData = this.graph.findDataById(curNodeId);
const newNodeData: BehaviorNodeModel = {
id: this.autoId++,
name: name,
};
if (!curNodeData.children) {
curNodeData.children = [];
}
curNodeData.children.push(Utils.createTreeData(newNodeData, settings));
this.changeWithoutAnim();
}
deleteNode() {
console.log("editor delete node");
const { curNodeId } = this.state;
if (!curNodeId) {
return;
}
if (curNodeId == "1") {
message.warn("根节点不能删除!");
return;
}
this.onSelectNode(null);
const rootData = this.graph.findDataById("1");
const parentData = Utils.findParent(rootData, curNodeId);
parentData.children = parentData.children.filter((e) => e.id != curNodeId);
this.changeWithoutAnim();
}
updateNode(id: string) {
this.graph.changeData();
this.graph.layout();
}
changeWithoutAnim() {
this.graph.set("animate", false);
this.graph.changeData();
this.graph.layout();
this.graph.set("animate", true);
}
save() {
const { filepath } = this.props;
const root = Utils.createFileData(this.graph.findDataById("1") as GraphNodeModel);
fs.writeFileSync(
filepath,
JSON.stringify({
name: path.basename(filepath).slice(0, -5),
root,
desc: root.desc,
})
);
}
copyNode() {
console.log("editor copy node");
const { curNodeId } = this.state;
if (!curNodeId) {
return;
}
const data = this.graph.findDataById(curNodeId);
clipboard.writeText(JSON.stringify(data, null, 2));
}
pasteNode() {
const { curNodeId } = this.state;
if (!curNodeId) {
message.warn("未选中节点");
return;
}
const curNodeData = this.graph.findDataById(curNodeId);
try {
const str = clipboard.readText();
if (!str || str == "") {
return;
}
const data = JSON.parse(str);
if (!curNodeData.children) {
curNodeData.children = [];
}
curNodeData.children.push(data);
this.autoId = Utils.refreshNodeId(this.graph.findDataById("1") as GraphNodeModel);
this.changeWithoutAnim();
} catch (error) {
message.error("粘贴数据有误");
}
}
render() {
const { curNodeId, treeModel, settings } = this.state;
var curNode: any;
if (curNodeId) {
curNode = this.graph.findDataById(curNodeId);
}
return (
<div className="editor">
<Row className="editorBd">
<Col span={18} className="editorContent" ref={this.ref} />
<Col span={6} className="editorSidebar">
{curNode ? (
<NodePanel
model={curNode}
settings={settings}
updateNode={(id, forceUpdate) => {
if (forceUpdate) {
const data: any = this.graph.findDataById(id);
data.conf = settings.getNodeConf(data.name);
data.size = Utils.calcTreeNodeSize(data);
this.changeWithoutAnim();
}
const item = this.graph.findById(id);
item.draw();
}}
/>
) : (
<TreePanel model={treeModel} />
)}
</Col>
</Row>
</div>
);
}
}
+64 -74
View File
@@ -1,99 +1,89 @@
import React, { Component } from "react";
import { Menu, Input } from "antd";
import * as fs from 'fs';
import * as path from 'path';
import * as fs from "fs";
import * as path from "path";
import { ipcRenderer } from "electron";
import MainEventType from "../common/MainEventType";
const { Search } = Input;
export interface TreeFile {
name: string;
path: string;
name: string;
path: string;
}
export interface PropertiesProps {
workdir: string;
onOpenTree: (path: string) => void;
workdir: string;
onOpenTree: (path: string) => void;
}
interface PropertiesState {
treeList: TreeFile[]
treeList: TreeFile[];
}
export default class Properties extends Component<PropertiesProps> {
state: PropertiesState = {
treeList: []
};
state: PropertiesState = {
treeList: [],
};
curWorkdir: string = '';
curWorkdir: string = "";
shouldComponentUpdate(nextProps: PropertiesProps) {
const shouldUpdate = this.curWorkdir != nextProps.workdir;
this.curWorkdir = nextProps.workdir;
return shouldUpdate;
}
componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_TREE, (event: any, path: string) => {
console.log("on Create tree", path);
this.props.onOpenTree(path);
this.forceUpdate();
});
var workdir = this.props.workdir;
if (workdir == '') {
return;
}
this.setState({ treeList: this.getTreeList(workdir) });
}
getTreeList(workdir: string) {
if (workdir == '' || !fs.existsSync(workdir)) {
console.log("workdir not exist", workdir);
return [];
shouldComponentUpdate(nextProps: PropertiesProps) {
const shouldUpdate = this.curWorkdir != nextProps.workdir;
this.curWorkdir = nextProps.workdir;
return shouldUpdate;
}
const files = fs.readdirSync(workdir);
const list: TreeFile[] = [];
files.forEach((filename) => {
const stat = fs.statSync(path.join(workdir, filename))
if (stat.isFile()) {
var name = filename.slice(0, -5)
list.push({
name,
path: workdir + '/' + name + '.json'
componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_TREE, (event: any, path: string) => {
console.log("on Create tree", path);
this.props.onOpenTree(path);
this.forceUpdate();
});
}
})
return list;
}
render() {
console.log("render Properties");
const { onOpenTree, workdir } = this.props;
const treeList = this.getTreeList(workdir);
return (
<div>
<Search
placeholder="Search"
onChange={() => {
var workdir = this.props.workdir;
if (workdir == "") {
return;
}
this.setState({ treeList: this.getTreeList(workdir) });
}
}}
/>
<Menu
mode="inline"
>
{treeList.map((tree) => (
<Menu.Item
key={tree.name}
onClick={() => onOpenTree(tree.path)}
>
{tree.name}
</Menu.Item>
))}
</Menu>
</div>
)
}
getTreeList(workdir: string) {
if (workdir == "" || !fs.existsSync(workdir)) {
console.log("workdir not exist", workdir);
return [];
}
const files = fs.readdirSync(workdir);
const list: TreeFile[] = [];
files.forEach((filename) => {
const stat = fs.statSync(path.join(workdir, filename));
if (stat.isFile()) {
var name = filename.slice(0, -5);
list.push({
name,
path: workdir + "/" + name + ".json",
});
}
});
return list;
}
render() {
console.log("render Properties");
const { onOpenTree, workdir } = this.props;
const treeList = this.getTreeList(workdir);
return (
<div>
<Search placeholder="Search" onChange={() => {}} />
<Menu mode="inline">
{treeList.map((tree) => (
<Menu.Item key={tree.name} onClick={() => onOpenTree(tree.path)}>
{tree.name}
</Menu.Item>
))}
</Menu>
</div>
);
}
}
+268 -270
View File
@@ -1,277 +1,275 @@
import G6 from '@antv/g6';
import Settings from '../main-process/Settings';
import G6 from "@antv/g6";
import Settings from "../main-process/Settings";
const NODE_COLORS: any = {
['Composite']: 'rgb(91,237,32)',
['Decorator']: 'rgb(218,167,16)',
['Condition']: 'rgb(228,20,139)',
['Action']: 'rgb(91,143,249)',
['Other']: 'rgb(112,112,112)',
}
["Composite"]: "rgb(91,237,32)",
["Decorator"]: "rgb(218,167,16)",
["Condition"]: "rgb(228,20,139)",
["Action"]: "rgb(91,143,249)",
["Other"]: "rgb(112,112,112)",
};
export default function (settings: Settings) {
const collapseStyle: any = {
'collapse-icon': {
// symbol: G6.Marker.expand,
fill: 'blue',
}
};
G6.registerNode(
'TreeNode',
{
options: {
type: 'rect',
labelCfg: {
style: {
fill: 'blue',
fontSize: 10
}
const collapseStyle: any = {
"collapse-icon": {
// symbol: G6.Marker.expand,
fill: "blue",
},
style: {
fill: "white",
};
G6.registerNode(
"TreeNode",
{
options: {
type: "rect",
labelCfg: {
style: {
fill: "blue",
fontSize: 10,
},
},
style: {
fill: "white",
},
stateStyles: {
selected: {
"main-box": {
stroke: "yellow",
lineWidth: 5,
},
},
hover: {
stroke: "white",
lineWidth: 3,
},
dragSrc: {
fill: "gray",
},
dragRight: {
"drag-right": {
fillOpacity: 0.6,
},
},
dragUp: {
"drag-up": {
fillOpacity: 0.6,
},
},
dragDown: {
"drag-down": {
fillOpacity: 0.6,
},
},
},
},
draw(cfg, group) {
const nodeConf = settings.getNodeConf(cfg.name as string);
const classify = nodeConf.type || "Other";
const color = NODE_COLORS[classify] || "Other";
var size = cfg.size ? (cfg.size as number[]) : [150, 40];
const w = size[0];
const h = size[1];
const x0 = -w / 2;
const y0 = -h / 2;
const r = 2;
const shape = group.addShape("rect", {
attrs: {
x: x0,
y: y0,
width: w,
height: h,
stroke: color,
lineWidth: 2,
fill: "white",
radius: r,
},
name: "main-box",
draggable: true,
});
// name bg
group.addShape("rect", {
attrs: {
x: x0 + 1.5,
y: y0 + 1.5,
width: w - 3,
height: 16,
fill: color,
// radius: r,
},
name: "name-bg",
draggable: true,
});
// id text
group.addShape("text", {
attrs: {
textBaseline: "top",
x: x0 - 3,
y: -8,
fontSize: 20,
lineHeight: 20,
text: cfg.id,
textAlign: "right",
fill: "white",
stroke: "black",
lineWidth: 2,
},
name: "id-text",
});
// icon
var img = `./static/icons/${classify}.svg`;
group.addShape("image", {
attrs: {
x: x0 + 3,
y: y0 + 2,
height: 14,
width: 14,
img,
},
name: "node-icon",
});
// name text
group.addShape("text", {
attrs: {
textBaseline: "top",
x: x0 + 18,
y: y0 + 4,
fontWeight: 800,
lineHeight: 20,
text: cfg.name,
fill: "black",
},
name: "name-text",
});
var x = x0 + 2;
var y = y0 + 23;
// desc text
const desc = cfg.desc || nodeConf.desc;
if (desc) {
group.addShape("text", {
attrs: {
textBaseline: "top",
x,
y,
lineHeight: 20,
fontWeight: 800,
text: `备注:${desc}`,
fill: "black",
},
name: "desc-text",
});
}
const args: any = cfg.args;
if (nodeConf.args && args && Object.keys(args).length > 0) {
y += 20;
group.addShape("text", {
attrs: {
textBaseline: "top",
x,
y,
lineHeight: 20,
text: `参数:${JSON.stringify(args)}`,
fill: "black",
},
name: "args-text",
});
}
const input: [] = cfg.input ? (cfg.input as []) : [];
if (nodeConf.input && input.length > 0) {
y += 20;
group.addShape("text", {
attrs: {
textBaseline: "top",
x,
y,
lineHeight: 20,
text: `输入:${JSON.stringify(input)}`,
fill: "black",
},
name: "input-text",
});
}
const output: [] = cfg.output ? (cfg.output as []) : [];
if (nodeConf.output && output.length > 0) {
y += 20;
group.addShape("text", {
attrs: {
textBaseline: "top",
x,
y,
lineHeight: 20,
text: `输出:${JSON.stringify(output)}`,
fill: "black",
},
name: "output-text",
});
}
if (Array.isArray(cfg.children) && cfg.children.length > 0) {
group.addShape("marker", {
attrs: {
x: w / 2,
y: 0,
r: 6,
symbol: G6.Marker.collapse,
stroke: "#666",
lineWidth: 1,
fill: "#fff",
},
name: "collapse-icon",
});
}
group.addShape("rect", {
name: "drag-up",
attrs: {
x: -w / 2,
y: -h / 2,
width: w,
height: h / 2,
fill: "blue",
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
group.addShape("rect", {
name: "drag-down",
attrs: {
x: -w / 2,
y: 0,
width: w,
height: h / 2,
fill: "blue",
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
group.addShape("rect", {
name: "drag-right",
attrs: {
x: w * 0.1,
y: -h / 2,
width: w * 0.4,
height: h,
fill: "blue",
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
return shape;
},
},
stateStyles: {
selected: {
'main-box': {
stroke: 'yellow',
lineWidth: 5,
},
},
hover: {
stroke: 'white',
lineWidth: 3,
},
dragSrc: {
fill: 'gray',
},
dragRight: {
'drag-right': {
fillOpacity: 0.6,
}
},
dragUp: {
'drag-up': {
fillOpacity: 0.6,
}
},
dragDown: {
'drag-down': {
fillOpacity: 0.6,
}
}
},
},
draw(cfg, group) {
const nodeConf = settings.getNodeConf(cfg.name as string);
const classify = nodeConf.type || 'Other';
const color = NODE_COLORS[classify] || 'Other';
var size = cfg.size ? cfg.size as number[] : [150, 40];
const w = size[0];
const h = size[1];
const x0 = -w / 2;
const y0 = -h / 2;
const r = 2;
const shape = group.addShape('rect', {
attrs: {
x: x0,
y: y0,
width: w,
height: h,
stroke: color,
lineWidth: 2,
fill: 'white',
radius: r,
},
name: 'main-box',
draggable: true,
});
// name bg
group.addShape('rect', {
attrs: {
x: x0 + 1.5,
y: y0 + 1.5,
width: w - 3,
height: 16,
fill: color,
// radius: r,
},
name: 'name-bg',
draggable: true,
});
// id text
group.addShape('text', {
attrs: {
textBaseline: 'top',
x: x0 - 3,
y: -8,
fontSize: 20,
lineHeight: 20,
text: cfg.id,
textAlign: 'right',
fill: 'white',
stroke: 'black',
lineWidth: 2,
},
name: 'id-text',
});
// icon
var img = `./static/icons/${classify}.svg`;
group.addShape('image', {
attrs: {
x: x0 + 3,
y: y0 + 2,
height: 14,
width: 14,
img,
},
name: 'node-icon',
});
// name text
group.addShape('text', {
attrs: {
textBaseline: 'top',
x: x0 + 18,
y: y0 + 4,
fontWeight: 800,
lineHeight: 20,
text: cfg.name,
fill: 'black',
},
name: 'name-text',
});
var x = x0 + 2;
var y = y0 + 23;
// desc text
const desc = cfg.desc || nodeConf.desc;
if (desc) {
group.addShape('text', {
attrs: {
textBaseline: 'top',
x,
y,
lineHeight: 20,
fontWeight: 800,
text: `备注:${desc}`,
fill: 'black',
},
name: 'desc-text',
});
}
const args: any = cfg.args;
if (nodeConf.args && args && Object.keys(args).length > 0) {
y += 20;
group.addShape('text', {
attrs: {
textBaseline: 'top',
x,
y,
lineHeight: 20,
text: `参数:${JSON.stringify(args)}`,
fill: 'black',
},
name: 'args-text',
});
}
const input: [] = cfg.input ? cfg.input as [] : [];
if (nodeConf.input && input.length > 0) {
y += 20;
group.addShape('text', {
attrs: {
textBaseline: 'top',
x,
y,
lineHeight: 20,
text: `输入:${JSON.stringify(input)}`,
fill: 'black',
},
name: 'input-text',
});
}
const output: [] = cfg.output ? cfg.output as [] : [];
if (nodeConf.output && output.length > 0) {
y += 20;
group.addShape('text', {
attrs: {
textBaseline: 'top',
x,
y,
lineHeight: 20,
text: `输出:${JSON.stringify(output)}`,
fill: 'black',
},
name: 'output-text',
});
}
if (Array.isArray(cfg.children) && cfg.children.length > 0) {
group.addShape('marker', {
attrs: {
x: w / 2,
y: 0,
r: 6,
symbol: G6.Marker.collapse,
stroke: '#666',
lineWidth: 1,
fill: '#fff',
},
name: 'collapse-icon',
});
}
group.addShape('rect', {
name: 'drag-up',
attrs: {
x: -w / 2,
y: -h / 2,
width: w,
height: h / 2,
fill: 'blue',
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
group.addShape('rect', {
name: 'drag-down',
attrs: {
x: -w / 2,
y: 0,
width: w,
height: h / 2,
fill: 'blue',
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
group.addShape('rect', {
name: 'drag-right',
attrs: {
x: w * 0.1,
y: -h / 2,
width: w * 0.4,
height: h,
fill: 'blue',
fillOpacity: 0,
},
draggable: true,
// visible: false,
});
return shape;
}
},
'single-node',
);
"single-node"
);
}
+109 -115
View File
@@ -1,136 +1,130 @@
import Editor from "./Editor";
import React, { Component } from "react";
import { Layout, Tabs, message } from 'antd';
import { ipcRenderer, remote } from 'electron';
import * as path from 'path';
import { Layout, Tabs, message } from "antd";
import { ipcRenderer, remote } from "electron";
import * as path from "path";
import 'antd/dist/antd.dark.css';
import "antd/dist/antd.dark.css";
import MainEventType from "../common/MainEventType";
const { TabPane } = Tabs;
interface TreeTabsProps {
}
interface TreeTabsProps {}
interface TreeTabsState {
filepaths: string[];
curPath?: string;
filepaths: string[];
curPath?: string;
}
export default class TreeTabs extends Component<TreeTabsProps, TreeTabsState> {
state: TreeTabsState = {
filepaths: [],
}
state: TreeTabsState = {
filepaths: [],
};
editors: { [path: string]: Editor } = {}
editors: { [path: string]: Editor } = {};
componentWillMount() {
}
componentWillMount() {}
componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_NODE, (event: any, name: any) => {
const editor = this.getCurEditor();
editor?.createNode(name);
});
componentDidMount() {
ipcRenderer.on(MainEventType.CREATE_NODE, (event: any, name: any) => {
const editor = this.getCurEditor();
editor?.createNode(name);
});
ipcRenderer.on(MainEventType.DELETE_NODE, () => {
const editor = this.getCurEditor();
editor?.deleteNode();
});
ipcRenderer.on(MainEventType.DELETE_NODE, () => {
const editor = this.getCurEditor();
editor?.deleteNode();
});
ipcRenderer.on(MainEventType.COPY_NODE, () => {
const editor = this.getCurEditor();
editor?.copyNode();
});
ipcRenderer.on(MainEventType.COPY_NODE, () => {
const editor = this.getCurEditor();
editor?.copyNode();
});
ipcRenderer.on(MainEventType.PASTE_NODE, () => {
const editor = this.getCurEditor();
editor?.pasteNode();
});
ipcRenderer.on(MainEventType.PASTE_NODE, () => {
const editor = this.getCurEditor();
editor?.pasteNode();
});
ipcRenderer.on(MainEventType.SAVE, (event: any) => {
const { curPath } = this.state;
if (!curPath) {
return;
}
const editor = this.editors[curPath];
editor.save();
message.success("已保存");
});
ipcRenderer.on(MainEventType.SAVE_ALL, (event: any) => {
for (let k in this.editors) {
let editor = this.editors[k];
editor.save();
}
message.success("已保存所有行为树");
});
}
getCurEditor() {
const { curPath } = this.state;
if (!curPath) {
return;
}
return this.editors[curPath];
}
openFile(path: string) {
console.log("on open file", path);
if (this.state.filepaths.indexOf(path) < 0) {
const filepaths = this.state.filepaths;
filepaths.push(path);
this.setState({ filepaths, curPath: path });
} else {
this.setState({ curPath: path });
}
}
render() {
console.log("render tabs");
const { filepaths, curPath } = this.state;
if (!curPath) {
return (<div />);
}
return (
<Tabs
hideAdd
className="tabs"
type="editable-card"
defaultActiveKey={curPath}
activeKey={curPath}
onChange={activeKey => {
this.setState({ curPath: activeKey })
}}
onEdit={(targetKey, action) => {
if (action == 'remove') {
const idx = filepaths.indexOf(targetKey as string);
if (idx < 0) {
return;
ipcRenderer.on(MainEventType.SAVE, (event: any) => {
const { curPath } = this.state;
if (!curPath) {
return;
}
filepaths.splice(idx, 1);
const nextPath = filepaths[idx - 1];
this.setState({ filepaths, curPath: nextPath });
}
}}
>
{
filepaths.map(filepath => {
return (
<TabPane tab={path.basename(filepath)} key={filepath}>
<Editor
filepath={filepath}
ref={
ref => {
this.editors[filepath] = ref;
}
}
/>
</TabPane>
);
})
const editor = this.editors[curPath];
editor.save();
message.success("已保存");
});
ipcRenderer.on(MainEventType.SAVE_ALL, (event: any) => {
for (let k in this.editors) {
let editor = this.editors[k];
editor.save();
}
message.success("已保存所有行为树");
});
}
getCurEditor() {
const { curPath } = this.state;
if (!curPath) {
return;
}
</Tabs>
)
}
};
return this.editors[curPath];
}
openFile(path: string) {
console.log("on open file", path);
if (this.state.filepaths.indexOf(path) < 0) {
const filepaths = this.state.filepaths;
filepaths.push(path);
this.setState({ filepaths, curPath: path });
} else {
this.setState({ curPath: path });
}
}
render() {
console.log("render tabs");
const { filepaths, curPath } = this.state;
if (!curPath) {
return <div />;
}
return (
<Tabs
hideAdd
className="tabs"
type="editable-card"
defaultActiveKey={curPath}
activeKey={curPath}
onChange={(activeKey) => {
this.setState({ curPath: activeKey });
}}
onEdit={(targetKey, action) => {
if (action == "remove") {
const idx = filepaths.indexOf(targetKey as string);
if (idx < 0) {
return;
}
filepaths.splice(idx, 1);
const nextPath = filepaths[idx - 1];
this.setState({ filepaths, curPath: nextPath });
}
}}
>
{filepaths.map((filepath) => {
return (
<TabPane tab={path.basename(filepath)} key={filepath}>
<Editor
filepath={filepath}
ref={(ref) => {
this.editors[filepath] = ref;
}}
/>
</TabPane>
);
})}
</Tabs>
);
}
}
+64 -69
View File
@@ -1,19 +1,18 @@
import * as ReactDOM from "react-dom";
import Editor from "./Editor";
import React, { Component } from "react";
import { Layout, Tabs } from 'antd';
import { ipcRenderer, remote } from 'electron';
import * as path from 'path';
import * as fs from 'fs';
import * as Utils from '../common/Utils';
import { Layout, Tabs } from "antd";
import { ipcRenderer, remote } from "electron";
import * as path from "path";
import * as fs from "fs";
import * as Utils from "../common/Utils";
import MainEventType from "../common/MainEventType";
import Properties from "./Properties";
import Settings from "../main-process/Settings";
import G6 from '@antv/g6';
import G6 from "@antv/g6";
import 'antd/dist/antd.dark.css';
import './index.css';
import "antd/dist/antd.dark.css";
import "./index.css";
import { ModelConfig, Item } from "@antv/g6/lib/types";
import RegisterNode from "./RegisterNode";
import TreeTabs from "./TreeTabs";
@@ -21,75 +20,71 @@ import TreeTabs from "./TreeTabs";
const { Header, Sider, Content, Footer } = Layout;
const { TabPane } = Tabs;
interface MainState {
workdir: string;
workspace: string;
workdir: string;
workspace: string;
}
export default class Main extends Component {
state: MainState = {
workdir: '',
workspace: '',
}
state: MainState = {
workdir: "",
workspace: "",
};
settings: Settings;
tabs: TreeTabs;
settings: Settings;
tabs: TreeTabs;
componentWillMount() {
this.updateSettings();
RegisterNode(this.settings);
}
componentWillMount() {
this.updateSettings();
RegisterNode(this.settings);
}
componentDidMount() {
ipcRenderer.on(MainEventType.OPEN_FILE, (event: any, path: any) => {
this.setState({ curPath: path });
});
componentDidMount() {
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);
document.title = workspace;
this.setState({ workdir, workspace });
});
ipcRenderer.on(MainEventType.OPEN_DIR, (event: any, workdir: any, workspace: string) => {
console.log("on open workspace", workspace);
document.title = workspace;
this.setState({ workdir, workspace });
});
console.log("workdir", this.settings.curWorkspace.getWorkdir());
this.setState({
workdir: this.settings.curWorkspace.getWorkdir(),
workspace: this.settings.curWorkspace.getFilepath(),
});
}
console.log("workdir", this.settings.curWorkspace.getWorkdir());
this.setState({
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");
const { workdir, workspace } = this.state;
document.title = `行为树编辑器 - ${workspace}`;
return (
<Layout className="body">
<Sider className="sider" width={250}>
<Properties
workdir={workdir}
onOpenTree={(path) => {
this.tabs.openFile(path);
}}
/>
</Sider>
<Content className="content">
<TreeTabs
ref={ref => {
this.tabs = ref;
}}
/>
</Content>
</Layout>
)
}
};
render() {
console.log("render main");
const { workdir, workspace } = this.state;
document.title = `行为树编辑器 - ${workspace}`;
return (
<Layout className="body">
<Sider className="sider" width={250}>
<Properties
workdir={workdir}
onOpenTree={(path) => {
this.tabs.openFile(path);
}}
/>
</Sider>
<Content className="content">
<TreeTabs
ref={(ref) => {
this.tabs = ref;
}}
/>
</Content>
</Layout>
);
}
}
ReactDOM.render(
<Main />,
document.getElementById('root') as HTMLElement
);
ReactDOM.render(<Main />, document.getElementById("root") as HTMLElement);