style: format codes

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

No files matched your search

+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>
);
}
}
+28 -33
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';
interface TreePanelProps {
model?: BehaviorTreeModel;
}
interface TreePanelState {
}
export default class TreePanel extends React.Component<TreePanelProps, TreePanelState> {
state: TreePanelState = {};
componentDidMount() {
}
render() {
const { model } = this.props;
if(!model) {
return (<div/>)
}
return (
<Card title="概况" style={{ height: "100%" }}>
行为树:{model.name}
</Card>
)
}
}
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;
}
interface TreePanelState {}
export default class TreePanel extends React.Component<TreePanelProps, TreePanelState> {
state: TreePanelState = {};
componentDidMount() {}
render() {
const { model } = this.props;
if (!model) {
return <div />;
}
return (
<Card title="概况" style={{ height: "100%" }}>
行为树:{model.name}
</Card>
);
}
}
+414 -412
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 './Editor.css';
import { clipboard } from 'electron';
export interface EditorProps {
filepath: string;
}
interface EditorState {
curNodeId?: string;
treeModel?: BehaviorTreeModel;
settings?: Settings;
}
export default class Editor extends React.Component<EditorProps, EditorState> {
private ref: React.RefObject<any>;
state: EditorState = {};
private graph: TreeGraph;
private dragSrcId: string;
private dragDstId: string;
private autoId: number;
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;
},
},
]
},
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();
}
const item = this.graph.findById(id);
item.draw();
}}
/>
:
<TreePanel
model={treeModel}
/>
}
</Col>
</Row>
</div>
);
}
}
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";
export interface EditorProps {
filepath: string;
}
interface EditorState {
curNodeId?: string;
treeModel?: BehaviorTreeModel;
settings?: Settings;
}
export default class Editor extends React.Component<EditorProps, EditorState> {
private ref: React.RefObject<any>;
state: EditorState = {};
private graph: TreeGraph;
private dragSrcId: string;
private dragDstId: string;
private autoId: number;
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;
},
},
],
},
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();
}
const item = this.graph.findById(id);
item.draw();
}}
/>
) : (
<TreePanel model={treeModel} />
)}
</Col>
</Row>
</div>
);
}
}