chore(project): 添加项目配置文件和忽略规则

- 添加 Babel 配置文件支持 ES6+ 语法转换
- 添加 ESLint 忽略规则和配置文件
- 添加 Git 忽略规则文件
- 添加 Travis CI 配置文件
- 添加 1.4.2 版本变更日志文件
- 添加 Helm 图表辅助模板文件
- 添加 Helm 忽略规则文件
This commit is contained in:
2026-03-27 17:36:48 +08:00
commit c2453d6434
1703 changed files with 277582 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"useBuiltIns": "usage",
"corejs": "core-js@3",
}
],
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-transform-runtime",
[
"@babel/plugin-proposal-class-properties",
{
"loose": true
}
],
[
"module-resolver",
{
"alias": {
"@common": "./ggeditor/common",
"@components": "./ggeditor/components",
"@helpers": "./ggeditor/helpers",
"@utils": "./ggeditor/utils",
"@gg-editor-core": "./ggeditor/gg-editor-core"
}
}
],
[
"transform-inline-environment-variables",
{
"include": [
"GG_EDITOR_VERSION"
]
}
]
]
}

View File

@@ -0,0 +1,4 @@
es
cjs
dist
scripts

View File

@@ -0,0 +1,26 @@
{
"env": {
"browser": true
},
"parser": "babel-eslint",
"extends": "airbnb",
"rules": {
"arrow-body-style": 0,
"class-methods-use-this": 0,
"func-names": 0,
"import/extensions": 0,
"import/no-extraneous-dependencies": 0,
"import/no-unresolved": 0,
"jsx-a11y/anchor-is-valid":0,
"jsx-a11y/no-static-element-interactions": 0,
"no-param-reassign": 0,
"no-plusplus": 0,
"object-curly-newline": 0,
"react/destructuring-assignment": 0,
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"react/no-multi-comp": 0,
"react/prefer-stateless-function": 0,
"react/prop-types": 0,
"react/sort-comp": 0
}
}

View File

@@ -0,0 +1,4 @@
es
cjs
dist
node_modules

View File

@@ -0,0 +1,13 @@
Copyright 1999-2019 Seata.io Group.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,25 @@
English | [简体中文](README.zh-CN.md)
# Seata Saga StateMachine Designer
A visual graph Seata Saga StateMachine Designer based on [GGEditor](https://github.com/alibaba/GGEditor).
## run
```sh
$ git clone https://github.com/seata/seata.git
$ cd saga/seata-saga-statemachine-designer
$ npm install
$ npm start
```
## build a package
```sh
$ cd saga/saga-statemachine-designer
$ npm build
```
copy 'index.html' and 'dist' directory to static html directory of web server
## Usage
To understand the state types of the state machine, please see [document of Saga](http://seata.io/zh-cn/docs/user/saga.html). After using the designer to complete the design of the state machine, you can click the 'Json View' button on the toolbar to switch to the Json view, and save the Json to the project of your own application. Although the Json generated by the designer is different from the standard Json of the Saga state machine (because the json generated by the designer has layout information), the state machine can be directly loaded and it will be converted into the Json of the Saga state machine standard.

View File

@@ -0,0 +1,25 @@
[English](README.md) | 简体中文
# Seata Saga StateMachine Designer
Seata Saga 状态机可视化图形设计器, 基于 [GGEditor](https://github.com/alibaba/GGEditor)
## 运行
```sh
$ git clone https://github.com/seata/seata.git
$ cd saga/seata-saga-statemachine-designer
$ npm install
$ npm start
```
## 打包
```sh
$ cd saga/saga-statemachine-designer
$ npm build
```
然后将index.html和dist目录拷贝到web server的静态页面目录下
## 使用
了解状态机的种状态类型请看Saga的[文档](http://seata.io/zh-cn/docs/user/saga.html)。 通过设计器完成设计后可以点击工具栏的'Json View'按钮切换到Json视图将Json拷贝保存到自己应用的工程里。虽然设计器生成的Json与Saga标准的Json有所差别因为设计器生成的json带有布局信息但状态机可以直接加载它会将其转化成Saga状态机标准的Json。

View File

@@ -0,0 +1,13 @@
const global = {
trackable: process.env.NODE_ENV === 'production',
version: process.env.GG_EDITOR_VERSION,
};
export default {
get(key) {
return global[key];
},
set(key, value) {
global[key] = value;
},
};

View File

@@ -0,0 +1,72 @@
export const FLOW_CONTAINER = 'J_FlowContainer';
export const MIND_CONTAINER = 'J_MindContainer';
export const KONI_CONTAINER = 'J_KoniContainer';
export const TOOLBAR_CONTAINER = 'J_ToolbarContainer';
export const MINIMAP_CONTAINER = 'J_MinimapContainer';
export const CONTEXT_MENU_CONTAINER = 'J_ContextMenuContainer';
export const FLOW_CLASS_NAME = 'Flow';
export const MIND_CLASS_NAME = 'Mind';
export const KONI_CLASS_NAME = 'Koni';
export const EVENT_BEFORE_ADD_PAGE = 'beforeAddPage';
export const EVENT_AFTER_ADD_PAGE = 'afterAddPage';
export const STATUS_CANVAS_SELECTED = 'canvas-selected';
export const STATUS_NODE_SELECTED = 'node-selected';
export const STATUS_EDGE_SELECTED = 'edge-selected';
export const STATUS_GROUP_SELECTED = 'group-selected';
export const STATUS_MULTI_SELECTED = 'multi-selected';
export const GRAPH_MOUSE_REACT_EVENTS = {
click: 'Click',
contextmenu: 'ContextMenu',
dblclick: 'DoubleClick',
drag: 'Drag',
dragend: 'DragEnd',
dragenter: 'DragEnter',
dragleave: 'DragLeave',
dragstart: 'DragStart',
drop: 'Drop',
mousedown: 'MouseDown',
mouseenter: 'MouseEnter',
mouseleave: 'MouseLeave',
mousemove: 'MouseMove',
mouseup: 'MouseUp',
};
export const GRAPH_OTHER_REACT_EVENTS = {
afterchange: 'onAfterChange',
afterchangesize: 'onAfterChangeSize',
afterviewportchange: 'onAfterViewportChange',
beforechange: 'onBeforeChange',
beforechangesize: 'onBeforeChangeSize',
beforeviewportchange: 'onBeforeViewportChange',
keydown: 'onKeyDown',
keyup: 'onKeyUp',
mousewheel: 'onMouseWheel',
};
export const PAGE_REACT_EVENTS = {
afteritemactived: 'onAfterItemActived',
afteriteminactivated: 'onAfterItemInactivated',
afteritemselected: 'onAfterItemSelected',
afteritemunactived: 'onAfterItemInactivated',
afteritemunselected: 'onAfterItemUnselected',
beforeitemactived: 'onBeforeItemActived',
beforeiteminactivated: 'onBeforeItemInactivated',
beforeitemselected: 'onBeforeItemSelected',
beforeitemunactived: 'onBeforeItemInactivated',
beforeitemunselected: 'onBeforeItemUnselected',
keyUpEditLabel: 'onKeyUpEditLabel',
};
export const EDITOR_REACT_EVENTS = {
aftercommandexecute: 'onAfterCommandExecute',
beforecommandexecute: 'onBeforeCommandExecute',
};
export const GRAPH_MOUSE_EVENTS = Object.keys(GRAPH_MOUSE_REACT_EVENTS);
export const GRAPH_OTHER_EVENTS = Object.keys(GRAPH_OTHER_REACT_EVENTS);
export const PAGE_EVENTS = Object.keys(PAGE_REACT_EVENTS);
export const EDITOR_EVENTS = Object.keys(EDITOR_REACT_EVENTS);

View File

@@ -0,0 +1,3 @@
import React from 'react';
export default React.createContext({});

View File

@@ -0,0 +1,18 @@
import React from 'react';
import GGEditorContext from '@common/context/GGEditorContext';
export default function (WrappedComponent) {
class InjectGGEditorContext extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return (
<GGEditorContext.Consumer>
{context => <WrappedComponent ref={forwardRef} {...rest} {...context} />}
</GGEditorContext.Consumer>
);
}
}
return React.forwardRef((props, ref) => <InjectGGEditorContext {...props} forwardRef={ref} />);
}

View File

@@ -0,0 +1,3 @@
import React from 'react';
export default React.createContext({});

View File

@@ -0,0 +1,21 @@
class PropsAPI {
editor = null;
constructor(editor) {
this.editor = editor;
['executeCommand'].forEach((key) => {
this[key] = (...params) => this.editor[key](...params);
});
['read', 'save', 'add', 'find', 'update', 'remove', 'getSelected'].forEach((key) => {
this[key] = (...params) => this.currentPage[key](...params);
});
}
get currentPage() {
return this.editor.getCurrentPage();
}
}
export default PropsAPI;

View File

@@ -0,0 +1,18 @@
import React from 'react';
import PropsAPIContext from '@common/context/PropsAPIContext';
export default function (WrappedComponent) {
class InjectPropsAPI extends React.Component {
render() {
const { forwardRef, ...rest } = this.props;
return (
<PropsAPIContext.Consumer>
{propsAPI => <WrappedComponent ref={forwardRef} {...rest} propsAPI={propsAPI} />}
</PropsAPIContext.Consumer>
);
}
}
return React.forwardRef((props, ref) => <InjectPropsAPI {...props} forwardRef={ref} />);
}

View File

@@ -0,0 +1,16 @@
import GGEditorCore from '@gg-editor-core/bundle';
import { EVENT_BEFORE_ADD_PAGE } from '@common/constants';
import track from '@helpers/track';
import { uniqueId } from '@utils';
export default class Editor extends GGEditorCore {
constructor(options) {
super(options);
this.id = uniqueId();
this.on(EVENT_BEFORE_ADD_PAGE, ({ className }) => {
track({ c1: className });
});
}
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
class Command extends React.Component {
render() {
const { name, children } = this.props;
return (
<div className="command" data-command={name}>
{children}
</div>
);
}
}
export default Command;

View File

@@ -0,0 +1,30 @@
import React from 'react';
class Menu extends React.Component {
static create = function (type) {
return class TypedMenu extends Menu {
constructor(props) {
super(props, type);
}
};
}
constructor(props, type) {
super(props);
this.type = type;
}
render() {
const { children } = this.props;
const { type } = this;
return (
<div className="menu" data-status={`${type}-selected`}>
{children}
</div>
);
}
}
export default Menu;

View File

@@ -0,0 +1,44 @@
import React from 'react';
import { pick } from '@utils';
import Editor from '@components/Base/Editor';
import { CONTEXT_MENU_CONTAINER } from '@common/constants';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
import Menu from './Menu';
class ContextMenu extends React.Component {
contextMenu = null;
get containerId() {
const { editor } = this.props;
return `${CONTEXT_MENU_CONTAINER}_${editor.id}`;
}
componentDidMount() {
const { editor } = this.props;
this.contextMenu = new Editor.Contextmenu({
container: this.containerId,
});
editor.add(this.contextMenu);
}
render() {
const { children } = this.props;
return (
<div id={this.containerId} {...pick(this.props, ['style', 'className'])}>
{children}
</div>
);
}
}
export const NodeMenu = Menu.create('node');
export const EdgeMenu = Menu.create('edge');
export const GroupMenu = Menu.create('group');
export const MultiMenu = Menu.create('multi');
export const CanvasMenu = Menu.create('canvas');
export default withGGEditorContext(ContextMenu);

View File

@@ -0,0 +1,35 @@
import React from 'react';
import { pick } from '@utils';
class Panel extends React.Component {
static create = function (type) {
return class TypedPanel extends Panel {
constructor(props) {
super(props, type);
}
};
}
constructor(props, type) {
super(props);
this.type = type;
}
render() {
const { status, children } = this.props;
const { type } = this;
if (`${type}-selected` !== status) {
return null;
}
return (
<div {...pick(this.props, ['style', 'className'])}>
{children}
</div>
);
}
}
export default Panel;

View File

@@ -0,0 +1,58 @@
import React from 'react';
import { pick } from '@utils';
import { STATUS_CANVAS_SELECTED } from '@common/constants';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
import Panel from './Panel';
class DetailPanel extends React.Component {
state = {
status: '',
}
constructor(props) {
super(props);
this.bindEvent();
}
bindEvent() {
const { onAfterAddPage } = this.props;
onAfterAddPage(({ page }) => {
this.setState({
status: STATUS_CANVAS_SELECTED,
});
page.on('statuschange', ({ status }) => {
this.setState({ status });
});
});
}
render() {
const { children } = this.props;
const { status } = this.state;
if (!status) {
return null;
}
return (
<div {...pick(this.props, ['style', 'className'])}>
{
React.Children.toArray(children).map(child => React.cloneElement(child, {
status,
}))
}
</div>
);
}
}
export const NodePanel = Panel.create('node');
export const EdgePanel = Panel.create('edge');
export const GroupPanel = Panel.create('group');
export const MultiPanel = Panel.create('multi');
export const CanvasPanel = Panel.create('canvas');
export default withGGEditorContext(DetailPanel);

View File

@@ -0,0 +1,38 @@
import Editor from '@components/Base/Editor';
import {
FLOW_CONTAINER,
FLOW_CLASS_NAME,
EVENT_BEFORE_ADD_PAGE,
EVENT_AFTER_ADD_PAGE,
} from '@common/constants';
import Page from '@components/Page';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Flow extends Page {
static defaultProps = {
data: {
nodes: [],
edges: [],
},
};
get pageId() {
const { editor } = this.props;
return `${FLOW_CONTAINER}_${editor.id}`;
}
initPage() {
const { editor } = this.props;
editor.emit(EVENT_BEFORE_ADD_PAGE, { className: FLOW_CLASS_NAME });
this.page = new Editor.Flow(this.config);
editor.add(this.page);
editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page });
}
}
export default withGGEditorContext(Flow);

View File

@@ -0,0 +1,85 @@
import React from 'react';
import Editor from '@components/Base/Editor';
import {
EDITOR_EVENTS,
EDITOR_REACT_EVENTS,
EVENT_BEFORE_ADD_PAGE,
EVENT_AFTER_ADD_PAGE,
} from '@common/constants';
import { pick } from '@utils';
import Global from '@common/Global';
import GGEditorContext from '@common/context/GGEditorContext';
import PropsAPIContext from '@common/context/PropsAPIContext';
import PropsAPI from '@common/context/PropsAPIContext/propsAPI';
class GGEditor extends React.Component {
static setTrackable(value) {
Global.set('trackable', Boolean(value));
}
editor = null;
get currentPage() {
return this.editor.getCurrentPage();
}
constructor(props) {
super(props);
this.init();
this.bindEvent();
}
addListener = (target, eventName, handler) => {
if (typeof handler === 'function') target.on(eventName, handler);
};
handleBeforeAddPage = (func) => {
this.editor.on(EVENT_BEFORE_ADD_PAGE, func);
};
handleAfterAddPage = (func) => {
const { currentPage: page } = this;
if (page) {
func({ page });
return;
}
this.editor.on(EVENT_AFTER_ADD_PAGE, func);
};
init() {
this.editor = new Editor();
this.ggEditor = {
editor: this.editor,
onBeforeAddPage: this.handleBeforeAddPage,
onAfterAddPage: this.handleAfterAddPage,
};
this.propsAPI = new PropsAPI(this.editor);
}
bindEvent() {
EDITOR_EVENTS.forEach((event) => {
this.addListener(this.editor, [event], this.props[EDITOR_REACT_EVENTS[event]]);
});
}
componentWillUnmount() {
this.editor.destroy();
}
render() {
const { children } = this.props;
return (
<GGEditorContext.Provider value={this.ggEditor}>
<PropsAPIContext.Provider value={this.propsAPI}>
<div {...pick(this.props, ['style', 'className'])}>{children}</div>
</PropsAPIContext.Provider>
</GGEditorContext.Provider>
);
}
}
export default GGEditor;

View File

@@ -0,0 +1,43 @@
import React from 'react';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Item extends React.Component {
constructor(props) {
super(props);
this.bindEvent();
}
handleMouseDown = () => {
const { type, size, shape, model } = this.props;
if (this.page) {
this.page.beginAdd(type, {
type,
size,
shape,
...model,
});
}
}
bindEvent() {
const { onAfterAddPage } = this.props;
onAfterAddPage(({ page }) => {
this.page = page;
});
}
render() {
const { src, shape, children } = this.props;
return (
<div style={{ cursor: 'pointer' }} onMouseDown={this.handleMouseDown}>
{src ? <img src={src} alt={shape} draggable={false} /> : children}
</div>
);
}
}
export default withGGEditorContext(Item);

View File

@@ -0,0 +1,46 @@
import React from 'react';
import { pick } from '@utils';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
import Item from './Item';
class ItemPanel extends React.Component {
page = null;
constructor(props) {
super(props);
this.bindEvent();
}
handleMouseUp = () => {
this.page.cancelAdd();
}
bindEvent() {
const { onAfterAddPage } = this.props;
onAfterAddPage(({ page }) => {
this.page = page;
document.addEventListener('mouseup', this.handleMouseUp);
});
}
componentWillUnmount() {
document.removeEventListener('mouseup', this.handleMouseUp);
}
render() {
const { children } = this.props;
return (
<div id={this.containerId} {...pick(this.props, ['style', 'className'])}>
{children}
</div>
);
}
}
export { Item };
export default withGGEditorContext(ItemPanel);

View File

@@ -0,0 +1,38 @@
import Editor from '@components/Base/Editor';
import {
KONI_CONTAINER,
KONI_CLASS_NAME,
EVENT_BEFORE_ADD_PAGE,
EVENT_AFTER_ADD_PAGE,
} from '@common/constants';
import Page from '@components/Page';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Koni extends Page {
static defaultProps = {
data: {
nodes: [],
edges: [],
},
};
get pageId() {
const { editor } = this.props;
return `${KONI_CONTAINER}_${editor.id}`;
}
initPage() {
const { editor } = this.props;
editor.emit(EVENT_BEFORE_ADD_PAGE, { className: KONI_CLASS_NAME });
this.page = new Editor.Koni(this.config);
editor.add(this.page);
editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page });
}
}
export default withGGEditorContext(Koni);

View File

@@ -0,0 +1,52 @@
import Editor from '@components/Base/Editor';
import {
MIND_CONTAINER,
MIND_CLASS_NAME,
EVENT_BEFORE_ADD_PAGE,
EVENT_AFTER_ADD_PAGE,
} from '@common/constants';
import Page from '@components/Page';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Mind extends Page {
get pageId() {
const { editor } = this.props;
return `${MIND_CONTAINER}_${editor.id}`;
}
initPage() {
const { editor } = this.props;
editor.emit(EVENT_BEFORE_ADD_PAGE, { className: MIND_CLASS_NAME });
this.page = new Editor.Mind(this.config);
editor.add(this.page);
editor.emit(EVENT_AFTER_ADD_PAGE, { page: this.page });
}
bindEvent() {
super.bindEvent();
this.bindKeyUpEditLabel();
}
bindKeyUpEditLabel() {
const editLabel = this.page.get('labelTextArea');
editLabel.on('keyup', (e) => {
e.stopPropagation();
const item = editLabel.focusItem;
const text = editLabel.textContent;
this.page.emit('keyUpEditLabel', {
item,
text,
});
});
}
}
export default withGGEditorContext(Mind);

View File

@@ -0,0 +1,89 @@
import React from 'react';
import G6 from '@antv/g6';
import { pick } from '@utils';
import { MINIMAP_CONTAINER } from '@common/constants';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
require('@antv/g6/build/plugin.tool.minimap');
const { Minimap: G6Minimap } = G6.Components;
class Minimap extends React.Component {
minimap = null;
get containerId() {
const { editor } = this.props;
return `${MINIMAP_CONTAINER}_${editor.id}`;
}
get currentPage() {
const { editor } = this.props;
return editor.getCurrentPage();
}
constructor(props) {
super(props);
this.bindEvent();
}
componentDidMount() {
this.init();
this.bindPage();
}
init() {
const {
container = this.containerId,
width,
height,
viewportWindowStyle,
viewportBackStyle,
} = this.props;
const { clientWidth, clientHeight } = document.getElementById(container);
this.minimap = new G6Minimap({
container,
width: width || clientWidth,
height: height || clientHeight,
viewportWindowStyle,
viewportBackStyle,
});
this.minimap.getGraph = () => this.currentPage.getGraph();
}
bindPage() {
if (!this.minimap || !this.currentPage) {
return;
}
const graph = this.currentPage.getGraph();
this.minimap.bindGraph(graph);
this.minimap.debounceRender();
}
bindEvent() {
const { onAfterAddPage } = this.props;
onAfterAddPage(() => {
this.bindPage();
});
}
render() {
const { container } = this.props;
if (container) {
return null;
}
return <div id={this.containerId} {...pick(this.props, ['style', 'className'])} />;
}
}
export default withGGEditorContext(Minimap);

View File

@@ -0,0 +1,121 @@
import React from 'react';
import { pick, merge } from '@utils';
import {
GRAPH_MOUSE_EVENTS,
GRAPH_OTHER_EVENTS,
PAGE_EVENTS,
GRAPH_MOUSE_REACT_EVENTS,
GRAPH_OTHER_REACT_EVENTS,
PAGE_REACT_EVENTS,
} from '@common/constants';
class Page extends React.Component {
page;
get pageId() {
return '';
}
config = {};
componentDidMount() {
this.init();
this.bindEvent();
this.forceUpdate();
}
shouldComponentUpdate(props) {
const { data: newData } = props;
const { data: oldData } = this.props;
const { mode: newMode } = props.graph || {};
const { mode: oldMode } = this.props.graph || {};
if (newMode !== oldMode) {
this.page.changeMode(newMode);
}
if (newData !== oldData) {
// Remove the arrow after the connection point of the compensation type
newData.edges.forEach((item) => {
if (item.type === 'Compensation') {
item.style = {
...item.style,
endArrow: false,
};
}
});
this.page.read(newData);
return true;
}
if (props.className !== this.props.className) return true;
return false;
}
get graph() {
return this.page.getGraph();
}
initPage() { }
readData() {
const { data } = this.config;
if (data) {
this.page.read(data);
}
}
addListener = (target, eventName, handler) => {
if (typeof handler === 'function') target.on(eventName, handler);
};
init() {
merge(this.config, this.props, {
graph: {
container: this.pageId,
},
});
this.initPage();
this.readData();
}
bindEvent() {
const { addListener } = this;
GRAPH_MOUSE_EVENTS.forEach((event) => {
const eventName = GRAPH_MOUSE_REACT_EVENTS[event];
addListener(this.graph, `${event}`, this.props[`on${eventName}`]);
addListener(this.graph, `node:${event}`, this.props[`onNode${eventName}`]);
addListener(this.graph, `edge:${event}`, this.props[`onEdge${eventName}`]);
addListener(this.graph, `group:${event}`, this.props[`onGroup${eventName}`]);
addListener(this.graph, `guide:${event}`, this.props[`onGuide${eventName}`]);
addListener(this.graph, `anchor:${event}`, this.props[`onAnchor${eventName}`]);
});
GRAPH_OTHER_EVENTS.forEach((event) => {
addListener(this.graph, [event], this.props[GRAPH_OTHER_REACT_EVENTS[event]]);
});
PAGE_EVENTS.forEach((event) => {
addListener(this.page, [event], this.props[PAGE_REACT_EVENTS[event]]);
});
}
render() {
const { page, pageId } = this;
const { children } = this.props;
return (
<div id={pageId} {...pick(this.props, ['style', 'className'])}>
{page ? children : null}
</div>
);
}
}
export default Page;

View File

@@ -0,0 +1,57 @@
import React from 'react';
import Editor from '@components/Base/Editor';
import { upperFirst } from '@utils';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Register extends React.Component {
static create = function (type) {
class TypedRegister extends Register {
constructor(props) {
super(props, type);
}
}
return withGGEditorContext(TypedRegister);
}
constructor(props, type) {
super(props);
this.type = type;
this.bindEvent();
}
bindEvent() {
const { type } = this;
const { onBeforeAddPage } = this.props;
onBeforeAddPage(({ className }) => {
let host = Editor[className];
let keys = ['name', 'config', 'extend'];
if (type === 'command') {
host = Editor;
}
if (type === 'behaviour') {
keys = ['name', 'behaviour', 'dependences'];
}
const args = keys.map(key => this.props[key]);
host[`register${upperFirst(type)}`](...args);
});
}
render() {
return null;
}
}
export const RegisterNode = Register.create('node');
export const RegisterEdge = Register.create('edge');
export const RegisterGroup = Register.create('group');
export const RegisterGuide = Register.create('guide');
export const RegisterCommand = Register.create('command');
export const RegisterBehaviour = Register.create('behaviour');

View File

@@ -0,0 +1,41 @@
import React from 'react';
import Editor from '@components/Base/Editor';
import { pick } from '@utils';
import { TOOLBAR_CONTAINER } from '@common/constants';
import withGGEditorContext from '@common/context/GGEditorContext/withGGEditorContext';
class Toolbar extends React.Component {
toolbar = null;
get containerId() {
const { editor } = this.props;
return `${TOOLBAR_CONTAINER}_${editor.id}`;
}
constructor(props) {
super(props);
const { editor, onAfterAddPage } = props;
onAfterAddPage(() => {
this.toolbar = new Editor.Toolbar({
container: this.containerId,
});
editor.add(this.toolbar);
});
}
render() {
const { children } = this.props;
return (
<div id={this.containerId} {...pick(this.props, ['style', 'className'])}>
{children}
</div>
);
}
}
export default withGGEditorContext(Toolbar);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,34 @@
import Global from '@common/Global';
import { toQueryString } from '@utils';
const BASE_URL = 'http://gm.mmstat.com/fsp.1.1';
const track = (options) => {
const trackable = Global.get('trackable');
const version = Global.get('version');
if (!trackable) {
return;
}
const { location, navigator } = window;
const image = new Image();
const params = toQueryString({
pid: 'ggeditor',
code: '11',
msg: 'syslog',
page: `${location.protocol}//${location.host}${location.pathname}`,
hash: location.hash,
ua: navigator.userAgent,
rel: version,
...options,
});
image.src = `${BASE_URL}?${params}`;
};
export default (options) => {
setTimeout(() => {
track(options);
}, 1000);
};

View File

@@ -0,0 +1,63 @@
import Flow from '@components/Flow';
import Mind from '@components/Mind';
import Koni from '@components/Koni';
import {
RegisterNode,
RegisterEdge,
RegisterGroup,
RegisterGuide,
RegisterCommand,
RegisterBehaviour,
} from '@components/Register';
import Command from '@components/Command';
import Minimap from '@components/Minimap';
import ContextMenu, {
NodeMenu,
EdgeMenu,
GroupMenu,
MultiMenu,
CanvasMenu,
} from '@components/ContextMenu';
import Toolbar from '@components/Toolbar';
import ItemPanel, { Item } from '@components/ItemPanel';
import DetailPanel, {
NodePanel,
EdgePanel,
GroupPanel,
MultiPanel,
CanvasPanel,
} from '@components/DetailPanel';
import withPropsAPI from '@common/context/PropsAPIContext/withPropsAPI';
import GGEditor from '@components/GGEditor';
export {
Flow,
Mind,
Koni,
RegisterNode,
RegisterEdge,
RegisterGroup,
RegisterGuide,
RegisterCommand,
RegisterBehaviour,
Command,
Minimap,
NodeMenu,
EdgeMenu,
GroupMenu,
MultiMenu,
CanvasMenu,
ContextMenu,
Toolbar,
Item,
ItemPanel,
NodePanel,
EdgePanel,
GroupPanel,
MultiPanel,
CanvasPanel,
DetailPanel,
withPropsAPI,
};
export default GGEditor;

View File

@@ -0,0 +1,14 @@
import merge from 'lodash/merge';
import pick from 'lodash/pick';
import uniqueId from 'lodash/uniqueId';
import upperFirst from 'lodash/upperFirst';
const toQueryString = obj => Object.keys(obj).map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`).join('&');
export {
merge,
pick,
toQueryString,
uniqueId,
upperFirst,
};

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Seata Saga StateMachine Designer</title>
<link rel="stylesheet" href="http://g.alicdn.com/code/lib/antd/3.16.1/antd.min.css">
</head>
<body>
<div id="root"></div>
<script src="http://g.alicdn.com/code/lib/react/16.8.6/umd/react.production.min.js"></script>
<script src="http://g.alicdn.com/code/lib/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="http://g.alicdn.com/code/lib/react-router-dom/5.0.0/react-router-dom.min.js"></script>
<script src="http://g.alicdn.com/code/lib/moment.js/2.24.0/moment.min.js"></script>
<script src="http://g.alicdn.com/code/lib/antd/3.16.1/antd.min.js"></script>
<script src="./dist/bundle.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,94 @@
{
"name": "seata-saga-statemachine-designer",
"version": "0.0.1",
"description": "A visual graph designer for Seata Saga StateMachine based on GGEdior 2.0.4",
"keywords": [
"react",
"graphics",
"designer",
"seata",
"saga",
"state machine"
],
"main": "cjs/index.js",
"module": "es/index.js",
"types": "typings/index.d.ts",
"files": [
"src",
"es",
"cjs",
"dist",
"*.md",
"typings"
],
"scripts": {
"start": "webpack-dev-server --config ./tools/webpack.config.dev.js --open",
"build": "rimraf ./dist && webpack --config ./tools/webpack.config.prod.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/seata/seata.git"
},
"authors": [
{
"name": "陈龙",
"email": "long_187@126.com"
},
{
"name": "高力",
"email": "3071730@qq.com"
}
],
"license": "Apache License, Version 2.0",
"bugs": {
"url": "https://github.com/seata/seata/issues"
},
"homepage": "http://seata.io/",
"peerDependencies": {
"react": "^16.3.0"
},
"dependencies": {
"@antv/g6": "^2.2.6",
"codemirror": "^5.55.0",
"core-js": "^3.6.5",
"lodash": "^4.17.10",
"react-codemirror": "^1.0.0"
},
"devDependencies": {
"@babel/cli": "^7.10.4",
"@babel/core": "^7.10.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/plugin-transform-modules-commonjs": "^7.10.4",
"@babel/plugin-transform-runtime": "^7.10.4",
"@babel/polyfill": "^7.10.4",
"@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@babel/runtime": "^7.10.4",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.1.0",
"babel-plugin-module-resolver": "^3.1.1",
"babel-plugin-transform-inline-environment-variables": "^0.4.3",
"cross-env": "^5.2.0",
"css-loader": "^2.1.0",
"cz-conventional-changelog": "^2.1.0",
"eslint": "^5.11.1",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jsx-a11y": "^6.3.1",
"eslint-plugin-react": "^7.20.3",
"less": "^3.11.3",
"less-loader": "^4.1.0",
"postcss-loader": "^3.0.0",
"postcss-preset-env": "^6.5.0",
"rimraf": "^2.6.2",
"style-loader": "^0.23.1",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.12",
"webpack-dev-server": "^3.11.0"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}
}

View File

@@ -0,0 +1,125 @@
import React from 'react';
import { withPropsAPI, Flow } from 'gg-editor';
import styles from './index.less';
import CodeMirror from 'react-codemirror';
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/javascript/javascript'
const nodeIndexes = {
Start: 1,
ServiceTask: 1,
ScriptTask: 1,
Compensation: 1,
Choice: 1,
Succeed: 1,
Fail: 1,
Catch: 1,
CompensationTrigger: 1,
SubStateMachine: 1
}
class WorkSpaceBase extends React.Component {
toGGEditorData(dataMap) {
const data = { nodes: [], edges: [] };
Object.values(dataMap).map(value => {
if (value.type && value.type == 'node') {
data.nodes[data.nodes.length] = value;
}
else if (value.source && value.target) {
data.edges[data.edges.length] = value;
}
});
return data;
};
changeFlowData(param) {
const { propsAPI, setFlowData } = this.props;
const { executeCommand, update } = propsAPI;
if (param.action == 'add' && param.item.type == 'edge') {
// Default polyline-round type @FIXME polyline-round has a bug
if (param.item.target
&& param.item.target.model
&& param.item.source
&& param.item.source.model) {
executeCommand(() => {
update(param.item, {
shape: 'flow-polyline-round',
});
});
}
if (param.item.target && param.item.target.model && param.item.target.model.stateType == 'Compensation') {
executeCommand(() => {
update(param.item, {
style: {
lineDash: "4",
},
type: 'Compensation',
});
});
}
else if (param.item.source && param.item.source.model) {
if (param.item.source.model.stateType == 'Choice') {
const choiceLinePropsTemplate = {
"Expression": "",
"Default": false
};
executeCommand(() => {
update(param.item, {
stateProps: choiceLinePropsTemplate
});
});
}
else if (param.item.source.model.stateType == 'Catch') {
const catchLinePropsTemplate = {
"Exceptions": ["java.lang.Throwable"]
};
executeCommand(() => {
update(param.item, {
stateProps: catchLinePropsTemplate
});
});
}
}
}
if (param.action == 'add' && param.item.type == 'node' && param.item.model) {
param.item.model.stateId = param.item.model.stateId + nodeIndexes[param.item.model.stateType]++;
if (param.item.model.stateType == 'ServiceTask'
|| param.item.model.stateType == 'Compensation'
|| param.item.model.stateType == 'ScriptTask'
|| param.item.model.stateType == 'SubStateMachine') {
param.item.model.label = param.item.model.stateId;
}
if (param.item.model.stateType == 'SubStateMachine') {
executeCommand(() => {
update(param.item, {
style: {
lineWidth: 2,
},
});
});
}
}
param.item && setFlowData(this.toGGEditorData(param.item.dataMap));
};
render() {
const { showJson, flowData, setFlowData } = this.props;
return <>
{showJson && <CodeMirror value={JSON.stringify(flowData, null, 2)} options={{
lineNumbers: true,
mode: 'javascript'
}} onChange={(newValue) => {
setFlowData(JSON.parse(newValue));
}}></CodeMirror>}
<Flow className={styles.flow + (showJson ? ' ' + styles.hidden : '')} data={flowData} onAfterChange={this.changeFlowData.bind(this)} />
</>
}
}
export const WorkSpace = withPropsAPI(WorkSpaceBase)

View File

@@ -0,0 +1,41 @@
import React, { useState } from 'react';
import { Row, Col } from 'antd';
import GGEditor from 'gg-editor';
import EditorMinimap from '../components/EditorMinimap';
import { FlowContextMenu } from '../components/EditorContextMenu';
import { FlowToolbar } from '../components/EditorToolbar';
import { FlowItemPanel } from '../components/EditorItemPanel';
import { FlowDetailPanel } from '../components/EditorDetailPanel';
import styles from './index.less';
import { WorkSpace } from './WorkSpace';
const FlowPage = () => {
const [showJson, setShowJson] = useState(false);
const [flowData, setFlowData] = useState({});
return (
<GGEditor className={styles.editor}>
<Row type="flex" className={styles.editorHd}>
<Col span={24}>
<FlowToolbar setShowJson={setShowJson} />
</Col>
</Row>
<Row type="flex" className={styles.editorBd}>
<Col span={3} className={styles.editorSidebar}>
<FlowItemPanel />
</Col>
<Col span={16} className={styles.editorContent}>
<WorkSpace showJson={showJson} flowData={flowData} setFlowData={setFlowData} />
</Col>
<Col span={5} className={styles.editorSidebar}>
<FlowDetailPanel />
<EditorMinimap />
</Col>
</Row>
<FlowContextMenu />
</GGEditor>
);
};
export default FlowPage;

View File

@@ -0,0 +1,57 @@
.editor {
display: flex;
flex-direction: column;
height: 100vh;
background: #fff;
}
.editorHd {
padding: 8px;
border: 1px solid #e6e9ed;
}
.editorBd {
flex: 1;
overflow: auto;
}
.editorSidebar,
.editorContent {
display: flex;
flex-direction: column;
}
.editorSidebar {
background: #fafafa;
&:first-child {
border-right: 1px solid #e6e9ed;
}
&:last-child {
border-left: 1px solid #e6e9ed;
}
}
.flow,
.mind,
.koni {
flex: 1;
// https://github.com/philipwalton/flexbugs/issues/197#issuecomment-378908438
height: 0;
}
.hidden {
display: none;
}
:global {
.ReactCodeMirror {
flex: 1;
.CodeMirror {
height: 100%;
}
}
}

View File

@@ -0,0 +1,13 @@
import { Icon } from 'antd';
const IconFont = Icon.createFromIconfontCN({
scriptUrl: 'https://at.alicdn.com/t/font_1101588_01zniftxm9yp.js',
});
export default IconFont;
export const IconFontExt = Icon.createFromIconfontCN({
scriptUrl: 'https://at.alicdn.com/t/font_1529997_6nr9bramkvp.js',
});

View File

@@ -0,0 +1,34 @@
import React from 'react';
import { NodeMenu, EdgeMenu, GroupMenu, MultiMenu, CanvasMenu, ContextMenu } from 'gg-editor';
import MenuItem from './MenuItem';
import styles from './index.less';
const FlowContextMenu = () => {
return (
<ContextMenu className={styles.contextMenu}>
<NodeMenu>
<MenuItem command="copy" />
<MenuItem command="delete" />
</NodeMenu>
<EdgeMenu>
<MenuItem command="delete" />
</EdgeMenu>
<GroupMenu>
<MenuItem command="copy" />
<MenuItem command="delete" />
</GroupMenu>
<MultiMenu>
<MenuItem command="copy" />
<MenuItem command="paste" />
<MenuItem command="delete" />
</MultiMenu>
<CanvasMenu>
<MenuItem command="undo" />
<MenuItem command="redo" />
<MenuItem command="pasteHere" icon="paste" text="Paste Here" />
</CanvasMenu>
</ContextMenu>
);
};
export default FlowContextMenu;

View File

@@ -0,0 +1,20 @@
import React from 'react';
import { Command } from 'gg-editor';
import upperFirst from 'lodash/upperFirst';
import IconFont from '../../common/IconFont';
import styles from './index.less';
const MenuItem = (props) => {
const { command, icon, text } = props;
return (
<Command name={command}>
<div className={styles.item}>
<IconFont type={`icon-${icon || command}`} />
<span>{text || upperFirst(command)}</span>
</div>
</Command>
);
};
export default MenuItem;

View File

@@ -0,0 +1,3 @@
import FlowContextMenu from './FlowContextMenu';
export { FlowContextMenu };

View File

@@ -0,0 +1,39 @@
.contextMenu {
display: none;
overflow: hidden;
background: #fff;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
.item {
display: flex;
align-items: center;
padding: 5px 12px;
cursor: pointer;
transition: all 0.3s;
user-select: none;
&:hover {
background: #e6f7ff;
}
i {
margin-right: 8px;
}
}
:global {
.disable {
:local {
.item {
color: rgba(0, 0, 0, 0.25);
cursor: auto;
&:hover {
background: #fff;
}
}
}
}
}
}

View File

@@ -0,0 +1,182 @@
import React, { Fragment } from 'react';
import { Card, Form, Input, Select, Button } from 'antd';
import { withPropsAPI } from 'gg-editor';
import upperFirst from 'lodash/upperFirst';
const { Item } = Form;
const { Option } = Select;
const { TextArea } = Input;
const inlineFormItemLayout = {
labelCol: {
sm: { span: 5 },
},
wrapperCol: {
sm: { span: 19 },
},
};
let lastSelectedItem;
class DetailForm extends React.Component {
get item() {
const { propsAPI } = this.props;
return propsAPI.getSelected()[0] ? propsAPI.getSelected()[0] : lastSelectedItem;
}
componentWillMount() {
// fix edit node details
this.operationDetail();
}
operationDetail = () => {
const { propsAPI, form } = this.props;
const currentPage = propsAPI.editor.getCurrentPage().getGraph();
currentPage.on('node:click', (e) => {
const { label, stateId, stateType, stateProps } = e.item.getModel();
lastSelectedItem = e.item;
form.setFieldsValue({ label, stateId, stateType, stateProps: JSON.stringify(stateProps, null, 2) });
});
currentPage.on('edge:click', (e) => {
const { label = '', shape = 'flow-smooth', stateProps } = e.item.getModel();
lastSelectedItem = e.item;
form.setFieldsValue({ label, shape, stateProps: JSON.stringify(stateProps, null, 2) });
});
}
handleSubmit = (e) => {
if (e && e.preventDefault) {
e.preventDefault();
}
const { form, propsAPI } = this.props;
const { executeCommand, update } = propsAPI;
setTimeout(() => {
form.validateFieldsAndScroll((err, values) => {
if (err) {
return;
}
const item = this.item;
if (!item) {
return;
}
if (values.stateProps) {
values.stateProps = JSON.parse(values.stateProps);
}
lastSelectedItem = item;
executeCommand(() => {
update(item, {
...values,
});
});
});
}, 0);
};
renderEdgeShapeSelect = () => {
return (
<Select onChange={this.handleSubmit}>
<Option value="flow-smooth">Smooth</Option>
<Option value="flow-polyline">Polyline</Option>
<Option value="flow-polyline-round">Polyline Round</Option>
</Select>
);
};
renderNodeDetail = () => {
const { form } = this.props;
const { label, stateId, stateType, stateProps } = this.item.getModel();
return (
<Fragment>
<Item label="Label" {...inlineFormItemLayout}>
{form.getFieldDecorator('label', {
initialValue: label,
})(<Input onBlur={this.handleSubmit} />)}
</Item>
<Item label="Id" {...inlineFormItemLayout}>
{form.getFieldDecorator('stateId', {
initialValue: stateId,
})(<Input onBlur={this.handleSubmit} />)}
</Item>
<Item label="Type" {...inlineFormItemLayout}>
{form.getFieldDecorator('stateType', {
initialValue: stateType,
})(<Input readOnly={true} />)}
</Item>
<Item label="Props" {...inlineFormItemLayout}>
{form.getFieldDecorator('stateProps', {
initialValue: JSON.stringify(stateProps, null, 2),
})(<TextArea onBlur={this.handleSubmit} rows={16} />)}
</Item>
<a target="_blank" style={{ float: 'right' }} href="http://seata.io/zh-cn/docs/user/saga.html">How to fill the properties?</a>
</Fragment >
);
};
renderEdgeDetail = () => {
const { form } = this.props;
const { label = '', shape = 'flow-smooth', stateProps } = this.item.getModel();
return (
<Fragment>
<Item label="Label" {...inlineFormItemLayout}>
{form.getFieldDecorator('label', {
initialValue: label,
})(<Input onBlur={this.handleSubmit} />)}
</Item>
<Item label="Shape" {...inlineFormItemLayout}>
{form.getFieldDecorator('shape', {
initialValue: shape,
})(this.renderEdgeShapeSelect())}
</Item>
<Item label="Props" {...inlineFormItemLayout}>
{form.getFieldDecorator('stateProps', {
initialValue: JSON.stringify(stateProps, null, 2),
})(<TextArea onBlur={this.handleSubmit} rows={16} />)}
</Item>
<a target="_blank" style={{ float: 'right' }} href="http://seata.io/zh-cn/docs/user/saga.html">How to fill the properties?</a>
</Fragment>
);
};
renderGroupDetail = () => {
const { form } = this.props;
const { label = 'New Group' } = this.item.getModel();
return (
<Item label="Label" {...inlineFormItemLayout}>
{form.getFieldDecorator('label', {
initialValue: label,
})(<Input onBlur={this.handleSubmit} />)}
</Item>
);
};
render() {
const { type } = this.props;
if (!this.item) {
return null;
}
return (
<Card type="inner" size="small" title={upperFirst(type)} bordered={false}>
<Form onSubmit={this.handleSubmit}>
{type === 'node' && this.renderNodeDetail()}
{type === 'edge' && this.renderEdgeDetail()}
{type === 'group' && this.renderGroupDetail()}
</Form>
</Card>
);
}
}
export default Form.create()(withPropsAPI(DetailForm));

View File

@@ -0,0 +1,29 @@
import React from 'react';
import { Card } from 'antd';
import { NodePanel, EdgePanel, GroupPanel, MultiPanel, CanvasPanel, DetailPanel } from 'gg-editor';
import DetailForm from './DetailForm';
import styles from './index.less';
const FlowDetailPanel = () => {
return (
<DetailPanel className={styles.detailPanel}>
<NodePanel>
<DetailForm type="node" />
</NodePanel>
<EdgePanel>
<DetailForm type="edge" />
</EdgePanel>
<GroupPanel>
<DetailForm type="group" />
</GroupPanel>
<MultiPanel>
<Card type="inner" size="small" title="Multi Select" bordered={false} />
</MultiPanel>
<CanvasPanel>
<Card type="inner" size="small" title="Canvas" bordered={false} />
</CanvasPanel>
</DetailPanel>
);
};
export default FlowDetailPanel;

View File

@@ -0,0 +1,3 @@
import FlowDetailPanel from './FlowDetailPanel';
export { FlowDetailPanel };

View File

@@ -0,0 +1,10 @@
.detailPanel {
flex: 1;
background: #fafafa;
:global {
.ant-card {
background: #fafafa;
}
}
}

View File

@@ -0,0 +1,196 @@
import React from 'react';
import { Card } from 'antd';
import { ItemPanel, Item } from 'gg-editor';
import styles from './index.less';
const startPropsTemplate = {
"StateMachine": {
"Name": "",
"Comment": "",
"Version": "0.0.1"
}
};
const serviceTaskPropsTemplate = {
"ServiceName": "",
"ServiceMethod": "",
"Input": [
{}
],
"Output": {
},
"Status": {
},
"Retry": [
]
};
const scriptTaskPropsTemplate = {
"ScriptType": "groovy",
"ScriptContent": "",
"Input": [
{}
],
"Output": {
}
};
const compensationPropsTemplate = {
"ServiceName": "",
"ServiceMethod": "",
"Input": [
{}
],
"Output": {
},
"Status": {
},
"Retry": [
]
};
const failPropsTemplate = {
"ErrorCode": "",
"Message": ""
};
const subStateMachinePropsTemplate = {
"StateMachineName": "",
"Input": [
{}
],
"Output": {
}
}
const FlowItemPanel = () => {
return (
<ItemPanel className={styles.itemPanel}>
<Card bordered={false}>
<Item
type="node"
size="72*72"
shape="flow-circle"
model={{
color: '#FA8C16',
label: 'Start',
stateId: 'Start',
stateType: 'Start',
stateProps: startPropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjaXJjbGUgaWQ9ImIiIGN4PSIzNiIgY3k9IjM2IiByPSIzNiIvPjxmaWx0ZXIgeD0iLTkuNyUiIHk9Ii02LjklIiB3aWR0aD0iMTE5LjQlIiBoZWlnaHQ9IjExOS40JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjRkZGMkU4IiB4bGluazpocmVmPSIjYiIvPjxjaXJjbGUgc3Ryb2tlPSIjRkZDMDY5IiBjeD0iMzYiIGN5PSIzNiIgcj0iMzUuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIyMyIgeT0iNDEiPlN0YXJ0PC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg=="
/>
<Item
type="node"
size="110*48"
shape="flow-rect"
model={{
color: '#1890FF',
label: 'ServiceTask',
stateId: 'ServiceTask',
stateType: 'ServiceTask',
stateProps: serviceTaskPropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE4IiBoZWlnaHQ9IjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cmVjdCBpZD0iYiIgeD0iMCIgeT0iMCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI0OCIgcng9IjQiLz48ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJhIj48ZmVPZmZzZXQgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyIiBpbj0ic2hhZG93T2Zmc2V0T3V0ZXIxIiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb21wb3NpdGUgaW49InNoYWRvd0JsdXJPdXRlcjEiIGluMj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJvdXQiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIvPjwvZmlsdGVyPjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx1c2UgZmlsbD0iIzAwMCIgZmlsdGVyPSJ1cmwoI2EpIiB4bGluazpocmVmPSIjYiIvPjx1c2UgZmlsbC1vcGFjaXR5PSIuOTIiIGZpbGw9IiNFNkY3RkYiIHhsaW5rOmhyZWY9IiNiIi8+PHJlY3Qgc3Ryb2tlPSIjMTg5MEZGIiB4PSIuNSIgeT0iLjUiIHdpZHRoPSIxMDkiIGhlaWdodD0iNDciIHJ4PSI0Ii8+PC9nPjx0ZXh0IGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii42NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHRzcGFuIHg9IjIxIiB5PSIyOSI+U2VydmljZVRhc2s8L3RzcGFuPjwvdGV4dD48L2c+PC9zdmc+"
/>
<Item
type="node"
size="110*48"
shape="flow-rect"
model={{
color: '#13C2C2',
label: 'ScriptTask',
stateId: 'ScriptTask',
stateType: 'ScriptTask',
stateProps: scriptTaskPropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE4IiBoZWlnaHQ9IjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cmVjdCBpZD0iYiIgeD0iMCIgeT0iMCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI0OCIgcng9IjQiLz48ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJhIj48ZmVPZmZzZXQgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyIiBpbj0ic2hhZG93T2Zmc2V0T3V0ZXIxIiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb21wb3NpdGUgaW49InNoYWRvd0JsdXJPdXRlcjEiIGluMj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJvdXQiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIvPjwvZmlsdGVyPjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx1c2UgZmlsbD0iIzAwMCIgZmlsdGVyPSJ1cmwoI2EpIiB4bGluazpocmVmPSIjYiIvPjx1c2UgZmlsbC1vcGFjaXR5PSIuOTIiIGZpbGw9IiNFNkZGRkIiIHhsaW5rOmhyZWY9IiNiIi8+PHJlY3Qgc3Ryb2tlPSIjNUNEQkQzIiB4PSIuNSIgeT0iLjUiIHdpZHRoPSIxMDkiIGhlaWdodD0iNDciIHJ4PSI0Ii8+PC9nPjx0ZXh0IGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii42NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHRzcGFuIHg9IjI0IiB5PSIyOSI+U2NyaXB0VGFzazwvdHNwYW4+PC90ZXh0PjwvZz48L3N2Zz4="
/>
<Item
type="node"
size="80*72"
shape="flow-rhombus"
model={{
color: '#13C2C2',
label: 'Choice',
stateId: 'Choice',
stateType: 'Choice'
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODYiIGhlaWdodD0iNzgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxwYXRoIGQ9Ik00Mi42NyAxLjY3bDM0Ljk2NSAzMS4zNTJhNCA0IDAgMCAxIDAgNS45NTZMNDIuNjcgNzAuMzNhNCA0IDAgMCAxLTUuMzQgMEwyLjM2NSAzOC45NzhhNCA0IDAgMCAxIDAtNS45NTZMMzcuMzMgMS42N2E0IDQgMCAwIDEgNS4zNCAweiIgaWQ9ImIiLz48ZmlsdGVyIHg9Ii04LjglIiB5PSItNi45JSIgd2lkdGg9IjExNy41JSIgaGVpZ2h0PSIxMTkuNCUiIGZpbHRlclVuaXRzPSJvYmplY3RCb3VuZGluZ0JveCIgaWQ9ImEiPjxmZU9mZnNldCBkeT0iMiIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd09mZnNldE91dGVyMSIvPjxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjIiIGluPSJzaGFkb3dPZmZzZXRPdXRlcjEiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbXBvc2l0ZSBpbj0ic2hhZG93Qmx1ck91dGVyMSIgaW4yPSJTb3VyY2VBbHBoYSIgb3BlcmF0b3I9Im91dCIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29sb3JNYXRyaXggdmFsdWVzPSIwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwLjA0IDAiIGluPSJzaGFkb3dCbHVyT3V0ZXIxIi8+PC9maWx0ZXI+PC9kZWZzPjxnIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+PGcgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMyAxKSI+PHVzZSBmaWxsPSIjMDAwIiBmaWx0ZXI9InVybCgjYSkiIHhsaW5rOmhyZWY9IiNiIi8+PHVzZSBmaWxsLW9wYWNpdHk9Ii45MiIgZmlsbD0iI0U2RkZGQiIgeGxpbms6aHJlZj0iI2IiLz48cGF0aCBzdHJva2U9IiM1Q0RCRDMiIGQ9Ik00Mi4zMzcgMi4wNDJhMy41IDMuNSAwIDAgMC00LjY3NCAwTDIuNjk4IDMzLjM5NGEzLjUgMy41IDAgMCAwIDAgNS4yMTJsMzQuOTY1IDMxLjM1MmEzLjUgMy41IDAgMCAwIDQuNjc0IDBsMzQuOTY1LTMxLjM1MmEzLjUgMy41IDAgMCAwIDAtNS4yMTJMNDIuMzM3IDIuMDQyeiIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDMgMSkiPjx0c3BhbiB4PSIyMSIgeT0iNDIiPkNob2ljZTwvdHNwYW4+PC90ZXh0PjwvZz48L3N2Zz4="
/>
<Item
type="node"
size="110*48"
shape="flow-capsule"
model={{
color: '#722ED1',
label: 'Compensation',
stateId: 'Compensation',
stateType: 'Compensation',
stateProps: compensationPropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE4IiBoZWlnaHQ9IjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cmVjdCBpZD0iYiIgeD0iMCIgeT0iMCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI0OCIgcng9IjI0Ii8+PGZpbHRlciB4PSItOC44JSIgeT0iLTEwLjQlIiB3aWR0aD0iMTE3LjUlIiBoZWlnaHQ9IjEyOS4yJSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjRjlGMEZGIiB4bGluazpocmVmPSIjYiIvPjxyZWN0IHN0cm9rZT0iI0IzN0ZFQiIgeD0iLjUiIHk9Ii41IiB3aWR0aD0iMTA5IiBoZWlnaHQ9IjQ3IiByeD0iMjMuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIxNSIgeT0iMjkiPkNvbXBlbnNhdGlvbjwvdHNwYW4+PC90ZXh0PjwvZz48L3N2Zz4="
/>
<Item
type="node"
size="72*72"
shape="flow-circle"
model={{
color: '#05A465',
label: 'Succeed',
stateId: 'Succeed',
stateType: 'Succeed',
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjaXJjbGUgaWQ9ImIiIGN4PSIzNiIgY3k9IjM2IiByPSIzNiIvPjxmaWx0ZXIgeD0iLTkuNyUiIHk9Ii02LjklIiB3aWR0aD0iMTE5LjQlIiBoZWlnaHQ9IjExOS40JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjQ0ZFNEQ4IiB4bGluazpocmVmPSIjYiIvPjxjaXJjbGUgc3Ryb2tlPSIjMDVBNDY1IiBjeD0iMzYiIGN5PSIzNiIgcj0iMzUuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIxMyIgeT0iNDEiPlN1Y2NlZWQ8L3RzcGFuPjwvdGV4dD48L2c+PC9zdmc+"
/>
<Item
type="node"
size="72*72"
shape="flow-circle"
model={{
color: 'red',
label: 'Fail',
stateId: 'Fail',
stateType: 'Fail',
stateProps: failPropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iODAiIGhlaWdodD0iODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjaXJjbGUgaWQ9ImIiIGN4PSIzNiIgY3k9IjM2IiByPSIzNiIvPjxmaWx0ZXIgeD0iLTkuNyUiIHk9Ii02LjklIiB3aWR0aD0iMTE5LjQlIiBoZWlnaHQ9IjExOS40JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjRkVFQUU3IiB4bGluazpocmVmPSIjYiIvPjxjaXJjbGUgc3Ryb2tlPSJyZWQiIGN4PSIzNiIgY3k9IjM2IiByPSIzNS41Ii8+PC9nPjx0ZXh0IGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii42NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHRzcGFuIHg9IjI2IiB5PSI0MSI+RmFpbDwvdHNwYW4+PC90ZXh0PjwvZz48L3N2Zz4="
/>
<Item
type="node"
size="39*39"
shape="flow-circle"
model={{
color: 'red',
label: 'Catch',
stateId: 'Catch',
stateType: 'Catch'
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDUiIGhlaWdodD0iNDUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxkZWZzPjxjaXJjbGUgaWQ9ImIiIGN4PSIyMCIgY3k9IjIwIiByPSIyMCIvPjxmaWx0ZXIgeD0iLTkuNyUiIHk9Ii02LjklIiB3aWR0aD0iMTE5LjQlIiBoZWlnaHQ9IjExOS40JSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjRkVFQUU3IiB4bGluazpocmVmPSIjYiIvPjxjaXJjbGUgc3Ryb2tlPSJyZWQiIGN4PSIxOS41IiBjeT0iMTkuNSIgcj0iMTkuNSIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSIzIiB5PSIyNCI+Q2F0Y2g8L3RzcGFuPjwvdGV4dD48L2c+PC9zdmc+"
/>
<Item
type="node"
size="110*48"
shape="flow-capsule"
model={{
color: 'red',
label: 'Compensation\nTrigger',
stateId: 'CompensationTrigger',
stateType: 'CompensationTrigger',
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE4IiBoZWlnaHQ9IjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cmVjdCBpZD0iYiIgeD0iMCIgeT0iMCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI0OCIgcng9IjI0Ii8+PGZpbHRlciB4PSItOC44JSIgeT0iLTEwLjQlIiB3aWR0aD0iMTE3LjUlIiBoZWlnaHQ9IjEyOS4yJSIgZmlsdGVyVW5pdHM9Im9iamVjdEJvdW5kaW5nQm94IiBpZD0iYSI+PGZlT2Zmc2V0IGR5PSIyIiBpbj0iU291cmNlQWxwaGEiIHJlc3VsdD0ic2hhZG93T2Zmc2V0T3V0ZXIxIi8+PGZlR2F1c3NpYW5CbHVyIHN0ZERldmlhdGlvbj0iMiIgaW49InNoYWRvd09mZnNldE91dGVyMSIgcmVzdWx0PSJzaGFkb3dCbHVyT3V0ZXIxIi8+PGZlQ29tcG9zaXRlIGluPSJzaGFkb3dCbHVyT3V0ZXIxIiBpbjI9IlNvdXJjZUFscGhhIiBvcGVyYXRvcj0ib3V0IiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb2xvck1hdHJpeCB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAuMDQgMCIgaW49InNoYWRvd0JsdXJPdXRlcjEiLz48L2ZpbHRlcj48L2RlZnM+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dXNlIGZpbGw9IiMwMDAiIGZpbHRlcj0idXJsKCNhKSIgeGxpbms6aHJlZj0iI2IiLz48dXNlIGZpbGwtb3BhY2l0eT0iLjkyIiBmaWxsPSIjRkVFQUU3IiB4bGluazpocmVmPSIjYiIvPjxyZWN0IHN0cm9rZT0icmVkIiB4PSIuNSIgeT0iLjUiIHdpZHRoPSIxMDkiIGhlaWdodD0iNDciIHJ4PSIyMy41Ii8+PC9nPjx0ZXh0IGZvbnQtZmFtaWx5PSJQaW5nRmFuZ1NDLVJlZ3VsYXIsIFBpbmdGYW5nIFNDIiBmb250LXNpemU9IjEyIiBmaWxsPSIjMDAwIiBmaWxsLW9wYWNpdHk9Ii42NSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCAyKSI+PHRzcGFuIHg9IjE1IiB5PSIyMiI+Q29tcGVuc2F0aW9uPC90c3Bhbj48L3RleHQ+PHRleHQgZm9udC1mYW1pbHk9IlBpbmdGYW5nU0MtUmVndWxhciwgUGluZ0ZhbmcgU0MiIGZvbnQtc2l6ZT0iMTIiIGZpbGw9IiMwMDAiIGZpbGwtb3BhY2l0eT0iLjY1IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0IDIpIj48dHNwYW4geD0iMzYiIHk9IjM2Ij5UcmlnZ2VyPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg=="
/>
<Item
type="node"
size="110*48"
shape="flow-rect"
model={{
color: '#FA8C16',
label: 'SubStateMachine',
stateId: 'SubStateMachine',
stateType: 'SubStateMachine',
stateProps: subStateMachinePropsTemplate,
}}
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTE4IiBoZWlnaHQ9IjU2IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj48ZGVmcz48cmVjdCBpZD0iYiIgeD0iMCIgeT0iMCIgd2lkdGg9IjExMCIgaGVpZ2h0PSI0OCIgcng9IjQiLz48ZmlsdGVyIHg9Ii04LjglIiB5PSItMTAuNCUiIHdpZHRoPSIxMTcuNSUiIGhlaWdodD0iMTI5LjIlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJhIj48ZmVPZmZzZXQgZHk9IjIiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRPdXRlcjEiLz48ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyIiBpbj0ic2hhZG93T2Zmc2V0T3V0ZXIxIiByZXN1bHQ9InNoYWRvd0JsdXJPdXRlcjEiLz48ZmVDb21wb3NpdGUgaW49InNoYWRvd0JsdXJPdXRlcjEiIGluMj0iU291cmNlQWxwaGEiIG9wZXJhdG9yPSJvdXQiIHJlc3VsdD0ic2hhZG93Qmx1ck91dGVyMSIvPjxmZUNvbG9yTWF0cml4IHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMC4wNCAwIiBpbj0ic2hhZG93Qmx1ck91dGVyMSIvPjwvZmlsdGVyPjwvZGVmcz48ZyBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxnIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx1c2UgZmlsbD0iIzAwMCIgZmlsdGVyPSJ1cmwoI2EpIiB4bGluazpocmVmPSIjYiIvPjx1c2UgZmlsbC1vcGFjaXR5PSIuOTIiIGZpbGw9IiNGRkYyRTgiIHhsaW5rOmhyZWY9IiNiIi8+PHJlY3Qgc3Ryb2tlLXdpZHRoPSIycHgiIHN0cm9rZT0iI0ZGQzA2OSIgeD0iLjUiIHk9Ii41IiB3aWR0aD0iMTA5IiBoZWlnaHQ9IjQ3IiByeD0iNCIvPjwvZz48dGV4dCBmb250LWZhbWlseT0iUGluZ0ZhbmdTQy1SZWd1bGFyLCBQaW5nRmFuZyBTQyIgZm9udC1zaXplPSIxMiIgZmlsbD0iIzAwMCIgZmlsbC1vcGFjaXR5PSIuNjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDQgMikiPjx0c3BhbiB4PSI2IiB5PSIyOSI+U3ViU3RhdGVNYWNoaW5lPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg=="
/>
</Card>
</ItemPanel>
);
};
export default FlowItemPanel;

View File

@@ -0,0 +1,3 @@
import FlowItemPanel from './FlowItemPanel';
export { FlowItemPanel };

View File

@@ -0,0 +1,20 @@
.itemPanel {
flex: 1;
background: #fafafa;
:global {
.ant-card {
background: #fafafa;
}
.ant-card-body {
display: flex;
flex-direction: column;
align-items: center;
> div {
margin-bottom: 16px;
}
}
}
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import { Card } from 'antd';
import { Minimap } from 'gg-editor';
const EditorMinimap = () => {
return (
<Card type="inner" size="small" title="Minimap" bordered={false}>
<Minimap height={200} />
</Card>
);
};
export default EditorMinimap;

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { Divider } from 'antd';
import { Toolbar } from 'gg-editor';
import ToolbarButton from './ToolbarButton';
import styles from './index.less';
const FlowToolbar = ({ setShowJson }) => {
return (
<Toolbar className={styles.toolbar}>
<ToolbarButton command="undo" />
<ToolbarButton command="redo" />
<Divider type="vertical" />
<ToolbarButton command="copy" />
<ToolbarButton command="paste" />
<ToolbarButton command="delete" />
<Divider type="vertical" />
<ToolbarButton command="zoomIn" icon="zoom-in" text="Zoom In" />
<ToolbarButton command="zoomOut" icon="zoom-out" text="Zoom Out" />
<ToolbarButton command="autoZoom" icon="fit-map" text="Fit Map" />
<ToolbarButton command="resetZoom" icon="actual-size" text="Actual Size" />
<Divider type="vertical" />
<ToolbarButton command="toBack" icon="to-back" text="To Back" />
<ToolbarButton command="toFront" icon="to-front" text="To Front" />
<Divider type="vertical" />
<ToolbarButton command="multiSelect" icon="multi-select" text="Multi Select" />
<Divider type="vertical" />
<ToolbarButton command="switchWorkspace" icon="iconjson" text="Json View" onClick={() => setShowJson(true)} />
<ToolbarButton command="switchWorkspace" icon="icondesign" text="Designer View" onClick={() => setShowJson(false)} />
</Toolbar>
);
};
export default FlowToolbar;

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { Tooltip } from 'antd';
import { Command } from 'gg-editor';
import upperFirst from 'lodash/upperFirst';
import IconFont from '../../common/IconFont';
import { IconFontExt } from '../../common/IconFont';
import styles from './index.less';
const ToolbarButton = (props) => {
const { command, icon, text, onClick } = props;
if (command == 'switchWorkspace') {
return (
<Tooltip className={styles.buttonExt}
title={text || upperFirst(command)}
onClick={onClick}
>
<IconFontExt type={`${icon || command}`} />
</Tooltip>
);
}
return (
<Command name={command}>
<Tooltip
title={text || upperFirst(command)}
placement="bottom"
overlayClassName={styles.tooltip}
>
<IconFont type={`icon-${icon || command}`} />
</Tooltip>
</Command>
);
};
export default ToolbarButton;

View File

@@ -0,0 +1,3 @@
import FlowToolbar from './FlowToolbar';
export { FlowToolbar };

View File

@@ -0,0 +1,54 @@
.toolbar {
display: flex;
align-items: center;
:global {
.command i {
display: inline-block;
width: 27px;
height: 27px;
margin: 0 6px;
padding-top: 6px;
text-align: center;
border: 1px solid #fff;
cursor: pointer;
&:hover {
border: 1px solid #e6e9ed;
}
}
.disable i {
color: rgba(0, 0, 0, 0.25);
cursor: auto;
&:hover {
border: 1px solid #fff;
}
}
}
}
.tooltip {
:global {
.ant-tooltip-inner {
font-size: 12px;
border-radius: 0;
}
}
}
.buttonExt {
display: inline-block;
width: 27px;
height: 27px;
margin: 0 6px;
padding-top: 6px;
text-align: center;
border: 1px solid #fff;
cursor: pointer;
&:hover {
border: 1px solid #e6e9ed;
}
}

View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter as Router, Route } from 'react-router-dom';
import FlowPage from './Flow';
ReactDOM.render(
<Router>
<Route path="/" component={FlowPage} />
</Router>,
document.getElementById('root'),
);

View File

@@ -0,0 +1,7 @@
const postcssPresetEnv = require('postcss-preset-env');
module.exports = {
plugins: [
postcssPresetEnv,
],
};

View File

@@ -0,0 +1,57 @@
const path = require('path');
const rules = [{
test: /\.js$/,
exclude: [
path.resolve(__dirname, 'node_modules'),
path.resolve(__dirname, '../ggeditor/gg-editor-core/bundle.js'),
],
use: {
loader: 'babel-loader',
},
}, {
test: /\.less$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
options: {
modules: true,
camelCase: true,
importLoaders: 1,
localIdentName: '[local]--[hash:base64:5]',
},
}, {
loader: 'postcss-loader',
options: {
config: {
path: path.resolve(__dirname, './postcss.config.js'),
},
},
}, {
loader: 'less-loader',
}],
}, {
test: /\.css$/,
use: [{
loader: 'style-loader',
}, {
loader: 'css-loader',
}],
}];
const externals = {
react: {
root: 'React',
commonjs: 'react',
commonjs2: 'react',
amd: 'react',
},
};
module.exports = {
module: {
rules,
},
externals,
};

View File

@@ -0,0 +1,60 @@
const path = require('path');
const { merge } = require('lodash');
const baseConfig = require('./webpack.config.base');
const mode = 'development';
const entry = {
bundle: path.resolve(__dirname, '..', 'src/index.js'),
};
const alias = {
'gg-editor': path.resolve(__dirname, '..', 'ggeditor'),
};
const externals = {
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom',
},
'react-router-dom': {
root: 'ReactRouterDOM',
commonjs: 'react-router-dom',
commonjs2: 'react-router-dom',
amd: 'react-router-dom',
},
antd: {
root: 'antd',
commonjs: 'antd',
commonjs2: 'antd',
amd: 'antd',
},
};
const devtool = 'cheap-module-eval-source-map';
const devServer = {
contentBase: path.resolve(__dirname, '..', ''),
publicPath: '/dist',
disableHostCheck: true,
};
const output = {
path: path.resolve(__dirname, '..', 'dist'),
filename: '[name].js',
libraryTarget: 'umd',
};
module.exports = merge(baseConfig, {
mode,
entry,
resolve: {
alias,
},
externals,
devtool,
devServer,
output,
});

View File

@@ -0,0 +1,53 @@
const path = require('path');
const { merge } = require('lodash');
const baseConfig = require('./webpack.config.base');
const mode = 'production';
const entry = {
bundle: path.resolve(__dirname, '..', 'src/index.js'),
};
const alias = {
'gg-editor': path.resolve(__dirname, '..', 'ggeditor'),
};
const externals = {
'react-dom': {
root: 'ReactDOM',
commonjs2: 'react-dom',
commonjs: 'react-dom',
amd: 'react-dom',
},
'react-router-dom': {
root: 'ReactRouterDOM',
commonjs: 'react-router-dom',
commonjs2: 'react-router-dom',
amd: 'react-router-dom',
},
antd: {
root: 'antd',
commonjs: 'antd',
commonjs2: 'antd',
amd: 'antd',
},
};
const devtool = 'cheap-module-source-map';
const output = {
path: path.resolve(__dirname, '..', 'dist'),
filename: '[name].js',
libraryTarget: 'umd',
};
module.exports = merge(baseConfig, {
mode,
entry,
resolve: {
alias,
},
externals,
devtool,
output,
});

View File

@@ -0,0 +1,283 @@
declare module 'gg-editor' {
export interface Align {
line?: any
item?: boolean | 'horizontal' | 'vertical' | 'center'
grid?: boolean | 'cc' | 'tl'
}
export interface Grid {
cell?: number
line?: any
}
export interface Shortcut {
clear?: boolean
selectAll?: boolean
undo?: boolean
redo?: boolean
delete?: boolean
zoomIn?: boolean
zoomOut?: boolean
autoZoom?: boolean
resetZoom?: boolean
toFront?: boolean
toBack?: boolean
copy?: boolean
paste?: boolean
multiSelect?: boolean
addGroup?: boolean
unGroup?: boolean
append?: boolean
appendChild?: boolean
collaspeExpand?: boolean
}
interface ReactProps {
style?: React.CSSProperties
className?: string
}
export interface BasicProps extends ReactProps {
/** 初始数据 */
data?: any
/** G6的配置项 @see https://www.yuque.com/antv/g6/graph */
graph?: any
/** 快捷键配置,内置命令 */
shortcut?: Shortcut
}
export interface FlowProps extends BasicProps {
/** 对齐配置 */
align?: Align
/** 网格线配置 */
grid?: Grid
/** default: true */
noEndEdge?: boolean
}
export interface MindProps extends BasicProps {
}
export interface KoniProps extends BasicProps {
}
export interface CommondProps {
name: string
}
export interface MninimapProps extends ReactProps {
/** 容器 id */
container?: string
width?: number
height?: number
/** 视窗样式,参考 G 绘图属性 */
viewportWindowStyle?: any
/** 背景样式,参考 G 绘图属性 */
viewportBackStyle?: any
}
export interface ItemPanelProps extends ReactProps {
/** 元素类型可选类型node edge */
type: string
/** 元素尺寸书写格式50*50 */
size: string
/** 元素形状内置形状node、edge */
shape: string
/** 元素初始 model */
model?: any
/** 元素概览 src */
src: string
}
export interface RegisterProps extends ReactProps{
/** 节点名称 */
name?: string
/** 节点配置 */
config?: any
/** 继承图形 */
extend?: string
}
export interface RegisterBehaviourProps extends ReactProps{
/** 行为名称 */
name?: string
/** 行为配置 function(page) */
behaviour?: any
/** 继承行为 */
dependences?: string[]
}
export interface GGEditorEvent {
action: 'add' | 'update' | 'remove' | 'changeData'
item?: any
shape?: any
x?: number
y?: number
domX?: number
domY?: number
/** DOM 原生事件 */
domeEvent?: any
/** drag 拖动图项 */
currentIem?: any
/** drag 拖动图形 */
currentShape?: any
/** mouseleave dragleave 到达的图形 */
toShape?: any
/** mouseleave dragleave 到达的图项 */
toItem?: any
}
/** 此类事件可以结合前缀 node、edge、group、guide、anchor 组合使用,例如: */
export interface GraphMouseReactEventsProps {
/** 鼠标左键点击事件 */
onClick?: (e: GGEditorEvent) => void
/** 鼠标左键双击事件 */
onDoubleClick?: (e: GGEditorEvent) => void
/** 鼠标移入事件 */
onMouseEnter?: (e: GGEditorEvent) => void
/** 鼠标移除事件 */
onMouseLeave?: (e: GGEditorEvent) => void
/** 鼠标按下事件 */
onMouseDown?: (e: GGEditorEvent) => void
/** 鼠标抬起事件 */
onMouseUp?: (e: GGEditorEvent) => void
/** 鼠标移动事件 */
onMouseMove?: (e: GGEditorEvent) => void
/** 鼠标开始拖拽事件 */
onDragStart?: (e: GGEditorEvent) => void
/** 鼠标拖拽事件 */
onDrag?: (e: GGEditorEvent) => void
/** 鼠标拖拽结束事件 */
onDragEnd?: (e: GGEditorEvent) => void
/** 鼠标拖拽进入事件 */
onDragEnter?: (e: GGEditorEvent) => void
/** 鼠标拖拽移出事件 */
onDragLeave?: (e: GGEditorEvent) => void
/** 鼠标拖拽放置事件 */
onDrop?: (e: GGEditorEvent) => void
/** 鼠标右键菜单事件 */
onContextMenu?: (e: GGEditorEvent) => void
}
export interface GraphOtherReactEventsProps {
/** 鼠标滚轮事件 */
onMouseWheel?: (e: GGEditorEvent) => void
/** 键盘按键按下事件 */
onKeyDown?: (e: GGEditorEvent) => void
/** 键盘按键抬起事件 */
onKeyUp?: (e: GGEditorEvent) => void
/** 子项数据变化前 */
onBeforeChange?: (e: GGEditorEvent) => void
/** 子项数据变化后 */
onAfterChange?: (e: GGEditorEvent) => void
/** 画布尺寸变化前 */
onBeforeChangeSize?: (e: GGEditorEvent) => void
/** 画布尺寸变化后 */
onAfterChangeSize?: (e: GGEditorEvent) => void
/** 视口变化前 */
onBeforeViewportChange?: (e: GGEditorEvent) => void
/** 视口变化后 */
onAfterViewportChange?: (e: GGEditorEvent) => void
/** 激活前 */
onBeforeItemActived?: (e: GGEditorEvent) => void
}
export interface PageReactEventsProps {
/** 激活后 */
onAfterItemActived?: (e: GGEditorEvent) => void
/** 取消激活前 */
onBeforeItemInactivated?: (e: GGEditorEvent) => void
/** 取消激活后 */
onAfterItemInactivated?: (e: GGEditorEvent) => void
/** 选中前 */
onBeforeItemSelected?: (e: GGEditorEvent) => void
/** 选中后 */
onAfterItemSelected?: (e: GGEditorEvent) => void
/** 取消选中前 */
onBeforeItemUnselected?: (e: GGEditorEvent) => void
/** 取消选中后 */
onAfterItemUnselected?: (e: GGEditorEvent) => void
/** 键盘按键抬起事件(节点编辑 */
onKeyUpEditLabel?: (e: GGEditorEvent) => void
}
export interface EditorCommand {
name: string
queue: boolean
}
export interface PropsApi {
propsApi: {
executeCommand?(command: EditorCommand)
read?(data: any)
save?(): any
add?(type: any, model: any)
find?(id: any)
update?(item: any, model: any)
remove?(item: any)
getSelected?()
}
}
export interface EditorReactEventsProps {
onAfterCommandExecute?: (e: EditorCommand) => void
onBeforeCommandExecute?: (e: EditorCommand) => void
}
export default class GGEditor extends React.Component<ReactProps & EditorReactEventsProps, any> {
static setTrackable(state: boolean)
}
/** 流程图 @see http://ggeditor.com/docs/api/flow.zh-CN.html */
export const Flow: React.ComponentClass<FlowProps & GraphMouseReactEventsProps & GraphOtherReactEventsProps & PageReactEventsProps, any>
/** 思维导图 @see http://ggeditor.com/docs/api/mind.zh-CN.html */
export const Mind: React.ComponentClass<MindProps & GraphMouseReactEventsProps & GraphOtherReactEventsProps & PageReactEventsProps, any>
/** 脑图 */
export const Koni: React.ComponentClass<KoniProps & GraphMouseReactEventsProps & GraphOtherReactEventsProps & PageReactEventsProps, any>
/** 此组件只能嵌套在 <Toolbar /> 或 <ContextMenu /> 组件内使用: @see http://ggeditor.com/docs/api/command.zh-CN.html */
export const Command: React.ComponentClass<CommondProps, any>
/** 不指定宽高的情况下则自动适应容器尺寸 @see http://ggeditor.com/docs/api/minimap.zh-CN.html */
export const Minimap: React.ComponentClass<MninimapProps, any>
/** 右键菜单,负责菜单显示隐藏,命令按钮绑定与可用禁用状态控制。 @see http://ggeditor.com/docs/api/contextMenu.zh-CN.html */
export const ContextMenu: React.ComponentClass<BasicProps, any>
/** 工具栏,负责命令按钮绑定与可用禁用状态控制。 @see http://ggeditor.com/docs/api/toolbar.zh-CN.html */
export const Toolbar: React.ComponentClass<BasicProps, any>
/** 元素面板栏 必需配合 <Item /> 组件使用,如果 <Item /> 包含 src 属性则自动显示元素概览图片。 @see http://ggeditor.com/docs/api/itemPanel.zh-CN.html */
export const ItemPanel: React.ComponentClass<ReactProps, any>
export const Item: React.ComponentClass<ItemPanelProps, any>
/** 属性栏会自动根据不同页面状态显示对应面板,例如:选中节点时则只会显示 NodePanel @see http://ggeditor.com/docs/api/detailPanel.zh-CN.html */
export const DetailPanel: React.ComponentClass<ReactProps, any>
export const RegisterNode: React.ComponentClass<RegisterProps, any>
export const RegisterEdge: React.ComponentClass<RegisterProps, any>
export const RegisterGroup: React.ComponentClass<RegisterProps, any>
export const RegisterCommand: React.ComponentClass<RegisterProps, any>
export const RegisterBehaviour: React.ComponentClass<RegisterBehaviourProps, any>
export const CanvasMenu: React.ComponentClass<ReactProps, any>
export const EdgeMenu: React.ComponentClass<ReactProps, any>
export const GroupMenu: React.ComponentClass<ReactProps, any>
export const MultiMenu: React.ComponentClass<ReactProps, any>
export const NodeMenu: React.ComponentClass<ReactProps, any>
export const CanvasPanel: React.ComponentClass<ReactProps, any>
export const EdgePanel: React.ComponentClass<ReactProps, any>
export const GroupPanel: React.ComponentClass<ReactProps, any>
export const MultiPanel: React.ComponentClass<ReactProps, any>
export const NodePanel: React.ComponentClass<ReactProps, any>
export const KoniCustomNode: React.ComponentClass<ReactProps & GraphMouseReactEventsProps & GraphOtherReactEventsProps & PageReactEventsProps, any>
/** 这里会带一个 Props 属性 @see http://ggeditor.com/docs/api/propsAPI.zh-CN.html */
export function withPropsAPI(com: React.ComponentClass<ReactProps, any>): React.ComponentClass<any, any>
}