ly165334
5 years ago
committed by
拾邑
23 changed files with 1690 additions and 123 deletions
@ -0,0 +1,16 @@ |
|||||
|
# http://editorconfig.org |
||||
|
root = true |
||||
|
|
||||
|
[*] |
||||
|
indent_style = space |
||||
|
indent_size = 2 |
||||
|
end_of_line = lf |
||||
|
charset = utf-8 |
||||
|
trim_trailing_whitespace = true |
||||
|
insert_final_newline = true |
||||
|
|
||||
|
[*.md] |
||||
|
trim_trailing_whitespace = false |
||||
|
|
||||
|
[Makefile] |
||||
|
indent_style = tab |
@ -0,0 +1,2 @@ |
|||||
|
BROWSER=none |
||||
|
ESLINT=1 |
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"extends": "eslint-config-umi" |
||||
|
} |
@ -1,104 +1,18 @@ |
|||||
# Logs |
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. |
||||
logs |
|
||||
*.log |
|
||||
npm-debug.log* |
|
||||
yarn-debug.log* |
|
||||
yarn-error.log* |
|
||||
lerna-debug.log* |
|
||||
|
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html) |
# dependencies |
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json |
/node_modules |
||||
|
/npm-debug.log* |
||||
|
/yarn-error.log |
||||
|
/yarn.lock |
||||
|
/package-lock.json |
||||
|
|
||||
# Runtime data |
# production |
||||
pids |
/dist |
||||
*.pid |
|
||||
*.seed |
|
||||
*.pid.lock |
|
||||
|
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover |
# misc |
||||
lib-cov |
.DS_Store |
||||
|
|
||||
# Coverage directory used by tools like istanbul |
# umi |
||||
coverage |
.umi |
||||
*.lcov |
.umi-production |
||||
|
|
||||
# nyc test coverage |
|
||||
.nyc_output |
|
||||
|
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) |
|
||||
.grunt |
|
||||
|
|
||||
# Bower dependency directory (https://bower.io/) |
|
||||
bower_components |
|
||||
|
|
||||
# node-waf configuration |
|
||||
.lock-wscript |
|
||||
|
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html) |
|
||||
build/Release |
|
||||
|
|
||||
# Dependency directories |
|
||||
node_modules/ |
|
||||
jspm_packages/ |
|
||||
|
|
||||
# TypeScript v1 declaration files |
|
||||
typings/ |
|
||||
|
|
||||
# TypeScript cache |
|
||||
*.tsbuildinfo |
|
||||
|
|
||||
# Optional npm cache directory |
|
||||
.npm |
|
||||
|
|
||||
# Optional eslint cache |
|
||||
.eslintcache |
|
||||
|
|
||||
# Microbundle cache |
|
||||
.rpt2_cache/ |
|
||||
.rts2_cache_cjs/ |
|
||||
.rts2_cache_es/ |
|
||||
.rts2_cache_umd/ |
|
||||
|
|
||||
# Optional REPL history |
|
||||
.node_repl_history |
|
||||
|
|
||||
# Output of 'npm pack' |
|
||||
*.tgz |
|
||||
|
|
||||
# Yarn Integrity file |
|
||||
.yarn-integrity |
|
||||
|
|
||||
# dotenv environment variables file |
|
||||
.env |
|
||||
.env.test |
|
||||
|
|
||||
# parcel-bundler cache (https://parceljs.org/) |
|
||||
.cache |
|
||||
|
|
||||
# Next.js build output |
|
||||
.next |
|
||||
|
|
||||
# Nuxt.js build / generate output |
|
||||
.nuxt |
|
||||
dist |
|
||||
|
|
||||
# Gatsby files |
|
||||
.cache/ |
|
||||
# Comment in the public line in if your project uses Gatsby and *not* Next.js |
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support |
|
||||
# public |
|
||||
|
|
||||
# vuepress build output |
|
||||
.vuepress/dist |
|
||||
|
|
||||
# Serverless directories |
|
||||
.serverless/ |
|
||||
|
|
||||
# FuseBox cache |
|
||||
.fusebox/ |
|
||||
|
|
||||
# DynamoDB Local files |
|
||||
.dynamodb/ |
|
||||
|
|
||||
# TernJS port file |
|
||||
.tern-port |
|
||||
|
@ -0,0 +1,37 @@ |
|||||
|
import { IConfig } from 'umi-types' |
||||
|
|
||||
|
// ref: https://umijs.org/config/
|
||||
|
const config: IConfig = { |
||||
|
treeShaking: true, |
||||
|
disableCSSModules: true, |
||||
|
plugins: [ |
||||
|
// ref: https://umijs.org/plugin/umi-plugin-react.html
|
||||
|
[ |
||||
|
'umi-plugin-react', |
||||
|
{ |
||||
|
antd: true, |
||||
|
dva: false, |
||||
|
dynamicImport: false, |
||||
|
title: 'iMove', |
||||
|
dll: false, |
||||
|
|
||||
|
routes: { |
||||
|
exclude: [/components\//], |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
], |
||||
|
extraBabelPlugins: [ |
||||
|
[ |
||||
|
'import', |
||||
|
{ |
||||
|
libraryName: '@antv/x6-components', |
||||
|
libraryDirectory: 'es', |
||||
|
transformToDefaultImport: false, |
||||
|
style: true, |
||||
|
}, |
||||
|
], |
||||
|
], |
||||
|
} |
||||
|
|
||||
|
export default config |
@ -1,21 +0,0 @@ |
|||||
MIT License |
|
||||
|
|
||||
Copyright (c) 2020 拾邑 |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
SOFTWARE. |
|
@ -1,2 +0,0 @@ |
|||||
# iMove |
|
||||
move your mouse, generate flow chart |
|
@ -0,0 +1,57 @@ |
|||||
|
{ |
||||
|
"private": true, |
||||
|
"name": "@antv/x6-example-features", |
||||
|
"version": "0.2.3", |
||||
|
"scripts": { |
||||
|
"start": "umi dev", |
||||
|
"build": "umi build", |
||||
|
"lint": "yarn lint:ts", |
||||
|
"lint:ts": "tslint -c tslint.json -p tsconfig.json --fix", |
||||
|
"precommit": "lint-staged" |
||||
|
}, |
||||
|
"dependencies": { |
||||
|
"@antv/x6": "^0.3.0", |
||||
|
"@antv/x6-components": "^0.2.0", |
||||
|
"@antv/x6-react-shape": "^0.2.0", |
||||
|
"antd": "^3.19.5", |
||||
|
"classnames": "^2.2.6", |
||||
|
"react": "^16.12.0", |
||||
|
"react-dom": "^16.8.6" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"@types/classnames": "^2.2.10", |
||||
|
"@types/jest": "^23.3.12", |
||||
|
"@types/react": "^16.7.18", |
||||
|
"@types/react-dom": "^16.0.11", |
||||
|
"@types/react-test-renderer": "^16.0.3", |
||||
|
"babel-eslint": "^9.0.0", |
||||
|
"babel-plugin-import": "^1.13.0", |
||||
|
"eslint": "^5.4.0", |
||||
|
"eslint-config-umi": "^1.4.0", |
||||
|
"eslint-plugin-flowtype": "^2.50.0", |
||||
|
"eslint-plugin-import": "^2.14.0", |
||||
|
"eslint-plugin-jsx-a11y": "^5.1.1", |
||||
|
"eslint-plugin-react": "^7.11.1", |
||||
|
"lint-staged": "^7.2.2", |
||||
|
"react-test-renderer": "^16.7.0", |
||||
|
"tslint": "^5.12.0", |
||||
|
"tslint-eslint-rules": "^5.4.0", |
||||
|
"tslint-react": "^3.6.0", |
||||
|
"umi": "^2.9.0", |
||||
|
"umi-plugin-react": "^1.8.0", |
||||
|
"umi-types": "^0.3.0" |
||||
|
}, |
||||
|
"lint-staged": { |
||||
|
"./src/**/*.{ts,tsx}": [ |
||||
|
"tslint -c tslint.json -p ./tsconfig.json --fix", |
||||
|
"git add" |
||||
|
], |
||||
|
"*.{js,jsx}": [ |
||||
|
"eslint --fix", |
||||
|
"git add" |
||||
|
] |
||||
|
}, |
||||
|
"engines": { |
||||
|
"node": ">=8.0.0" |
||||
|
} |
||||
|
} |
@ -0,0 +1,7 @@ |
|||||
|
html, body, #root { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
body { |
||||
|
margin: 0; |
||||
|
} |
@ -0,0 +1,398 @@ |
|||||
|
export interface DataItem { |
||||
|
width: number; |
||||
|
height: number; |
||||
|
data: { |
||||
|
type: string; |
||||
|
title: string; |
||||
|
icon?: string; |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
const items: DataItem[] = [ |
||||
|
{ |
||||
|
width: 56, |
||||
|
height: 56, |
||||
|
data: { |
||||
|
type: "start", |
||||
|
title: "开始" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
width: 56, |
||||
|
height: 56, |
||||
|
data: { |
||||
|
type: "end", |
||||
|
title: "结束" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
width: 72, |
||||
|
height: 72, |
||||
|
data: { |
||||
|
type: "status", |
||||
|
title: "状态节点" |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
width: 128, |
||||
|
height: 48, |
||||
|
data: { |
||||
|
type: "execute", |
||||
|
title: "执行节点", |
||||
|
icon: |
||||
|
'<svg viewBox="0 0 1024 1024" focusable="false" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M825.8 498L538.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L825.8 526c8.3-7.2 8.3-20.8 0-28zm-320 0L218.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L505.8 526c4.1-3.6 6.2-8.8 6.2-14 0-5.2-2.1-10.4-6.2-14z"></path></svg>' |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
width: 148, |
||||
|
height: 48, |
||||
|
data: { |
||||
|
type: "cond", |
||||
|
title: "条件节点", |
||||
|
icon: |
||||
|
'<svg viewBox="64 64 896 896" focusable="false" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M752 100c-61.8 0-112 50.2-112 112 0 47.7 29.9 88.5 72 104.6v27.6L512 601.4 312 344.2v-27.6c42.1-16.1 72-56.9 72-104.6 0-61.8-50.2-112-112-112s-112 50.2-112 112c0 50.6 33.8 93.5 80 107.3v34.4c0 9.7 3.3 19.3 9.3 27L476 672.3v33.6c-44.2 15-76 56.9-76 106.1 0 61.8 50.2 112 112 112s112-50.2 112-112c0-49.2-31.8-91-76-106.1v-33.6l226.7-291.6c6-7.7 9.3-17.3 9.3-27v-34.4c46.2-13.8 80-56.7 80-107.3 0-61.8-50.2-112-112-112zM224 212a48.01 48.01 0 0 1 96 0 48.01 48.01 0 0 1-96 0zm336 600a48.01 48.01 0 0 1-96 0 48.01 48.01 0 0 1 96 0zm192-552a48.01 48.01 0 0 1 0-96 48.01 48.01 0 0 1 0 96z"></path></svg>' |
||||
|
} |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
const map: { [key: string]: DataItem } = {}; |
||||
|
|
||||
|
items.forEach(item => { |
||||
|
map[item.data.type] = item; |
||||
|
}); |
||||
|
|
||||
|
export const data = { items, map }; |
||||
|
|
||||
|
export function isCircle(item: DataItem) { |
||||
|
return item.data.type === "start" || item.data.type === "end"; |
||||
|
} |
||||
|
|
||||
|
export function isGroup(type: string) { |
||||
|
return type === "combine" || type === "group"; |
||||
|
} |
||||
|
|
||||
|
export function isFunc(type: string) { |
||||
|
return type === "approvement" || type === "execute"; |
||||
|
} |
||||
|
|
||||
|
export const mockGraphData = { |
||||
|
nodes: [ |
||||
|
{ |
||||
|
id: "cell-2", |
||||
|
data: { |
||||
|
type: "start", |
||||
|
title: "开始" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: false, |
||||
|
perimeter: "ellipse", |
||||
|
visible: true, |
||||
|
x: -200, |
||||
|
y: -292, |
||||
|
width: 56, |
||||
|
height: 56, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-3", |
||||
|
data: { |
||||
|
type: "status", |
||||
|
title: "idle", |
||||
|
serviceType: "none" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -208, |
||||
|
y: -200, |
||||
|
width: 72, |
||||
|
height: 72, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-4", |
||||
|
data: { |
||||
|
type: "status", |
||||
|
title: "loading", |
||||
|
serviceType: "invoke", |
||||
|
api: "douban.api" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -208, |
||||
|
y: -70, |
||||
|
width: 72, |
||||
|
height: 72, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-5", |
||||
|
data: { |
||||
|
type: "execute", |
||||
|
title: "reject", |
||||
|
icon: |
||||
|
'<svg viewBox="0 0 1024 1024" focusable="false" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M825.8 498L538.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L825.8 526c8.3-7.2 8.3-20.8 0-28zm-320 0L218.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L505.8 526c4.1-3.6 6.2-8.8 6.2-14 0-5.2-2.1-10.4-6.2-14z"></path></svg>' |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -370, |
||||
|
y: 60, |
||||
|
width: 128, |
||||
|
height: 48, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-6", |
||||
|
data: { |
||||
|
type: "execute", |
||||
|
title: "resolve", |
||||
|
icon: |
||||
|
'<svg viewBox="0 0 1024 1024" focusable="false" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M825.8 498L538.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L825.8 526c8.3-7.2 8.3-20.8 0-28zm-320 0L218.4 249.9c-10.7-9.2-26.4-.9-26.4 14v496.3c0 14.9 15.7 23.2 26.4 14L505.8 526c4.1-3.6 6.2-8.8 6.2-14 0-5.2-2.1-10.4-6.2-14z"></path></svg>', |
||||
|
action: "empty", |
||||
|
contextKey: "file", |
||||
|
contextValue: "result.a" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -100, |
||||
|
y: 60, |
||||
|
width: 128, |
||||
|
height: 48, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-7", |
||||
|
data: { |
||||
|
type: "status", |
||||
|
title: "fail" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -342, |
||||
|
y: 190, |
||||
|
width: 72, |
||||
|
height: 72, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-9", |
||||
|
data: { |
||||
|
type: "status", |
||||
|
title: "success" |
||||
|
}, |
||||
|
shape: "html", |
||||
|
resizable: true, |
||||
|
perimeter: "rectangle", |
||||
|
visible: true, |
||||
|
x: -72, |
||||
|
y: 190, |
||||
|
width: 72, |
||||
|
height: 72, |
||||
|
relative: false, |
||||
|
translateControlPoints: true |
||||
|
} |
||||
|
], |
||||
|
edges: [ |
||||
|
{ |
||||
|
id: "cell-10", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-2", |
||||
|
target: "cell-3" |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-11", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-3", |
||||
|
target: "cell-4" |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-12", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-4", |
||||
|
target: "cell-5" |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-13", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-4", |
||||
|
target: "cell-6" |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-14", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-5", |
||||
|
target: "cell-7" |
||||
|
}, |
||||
|
{ |
||||
|
id: "cell-15", |
||||
|
data: null, |
||||
|
align: "center", |
||||
|
verticalAlign: "middle", |
||||
|
fontColor: "rgba(0, 0, 0, 1)", |
||||
|
shape: "connector", |
||||
|
endArrow: "classic", |
||||
|
stroke: "#888", |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false, |
||||
|
sourceAnchorX: "0.5", |
||||
|
sourceAnchorY: "1", |
||||
|
targetAnchorX: "0.5", |
||||
|
targetAnchorY: "0", |
||||
|
sourceAnchorDx: "0", |
||||
|
targetAnchorDx: "0", |
||||
|
targetAnchorDy: "0", |
||||
|
visible: true, |
||||
|
x: 0, |
||||
|
y: 0, |
||||
|
width: 0, |
||||
|
height: 0, |
||||
|
relative: true, |
||||
|
translateControlPoints: true, |
||||
|
source: "cell-6", |
||||
|
target: "cell-9" |
||||
|
} |
||||
|
] |
||||
|
}; |
@ -0,0 +1,171 @@ |
|||||
|
body { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.flowchart-wrap { |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.flowchart-main { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.flowchart-toolbar { |
||||
|
height: 41; |
||||
|
border-bottom: 1px solid #e9e9e9; |
||||
|
background-color: #fbfbfb; |
||||
|
padding: 4px 8px; |
||||
|
} |
||||
|
|
||||
|
.flowchart-graph { |
||||
|
flex: 1; |
||||
|
overflow: hidden; |
||||
|
|
||||
|
.graph { |
||||
|
box-shadow: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.flowchart-sidebar, |
||||
|
.flowchart-format { |
||||
|
background-color: #fbfbfb; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.flowchart-sidebar { |
||||
|
border-right: 1px solid #e9e9e9; |
||||
|
} |
||||
|
|
||||
|
.flowchart-format { |
||||
|
border-left: 1px solid #e9e9e9; |
||||
|
} |
||||
|
|
||||
|
.flowchart-sidebar-title { |
||||
|
height: 41px; |
||||
|
text-align: center; |
||||
|
border-bottom: 1px solid #e9e9e9; |
||||
|
font-weight: bold; |
||||
|
line-height: 40px; |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
.flowchart-sidebar-content { |
||||
|
flex: 1; |
||||
|
padding: 8px 16px; |
||||
|
} |
||||
|
|
||||
|
.x6-node { |
||||
|
foreignobject { |
||||
|
filter: drop-shadow(2px 4px 4px rgba(80, 80, 80, 0.15)); |
||||
|
overflow: visible; |
||||
|
|
||||
|
> div { |
||||
|
overflow: visible !important; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.flowchart-node-wrap { |
||||
|
overflow: hidden; |
||||
|
margin: 16px auto; |
||||
|
cursor: move; |
||||
|
|
||||
|
&.start, |
||||
|
&.end { |
||||
|
border-radius: 100%; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.flowchart-node { |
||||
|
font-family: 'PingFangSC-Regular', 'PingFang SC'; |
||||
|
font-size: 14px; |
||||
|
font-weight: 400; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
width: 100%; |
||||
|
height: 100%; |
||||
|
color: rgba(0, 0, 0, 0.85); |
||||
|
border: 1px solid #e9e9e9; |
||||
|
text-align: center; |
||||
|
// box-shadow: 0 0 0 1px rgba(34, 36, 38, 0.15); |
||||
|
background: #ffffff; |
||||
|
user-select: none; |
||||
|
border-radius: 4px; |
||||
|
|
||||
|
&.is-gruop { |
||||
|
align-items: flex-start; |
||||
|
|
||||
|
.icon { |
||||
|
margin-left: 8px; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.anticon { |
||||
|
font-size: 16px; |
||||
|
margin-left: 16px; |
||||
|
} |
||||
|
|
||||
|
.icon { |
||||
|
font-size: 16px; |
||||
|
margin-left: 16px; |
||||
|
font-style: normal; |
||||
|
line-height: 0; |
||||
|
display: inline-block; |
||||
|
text-align: center; |
||||
|
vertical-align: -0.125em; |
||||
|
text-transform: none; |
||||
|
color: #666666; |
||||
|
|
||||
|
text-rendering: optimizelegibility; |
||||
|
-webkit-font-smoothing: antialiased; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
&.start, |
||||
|
&.end { |
||||
|
border-radius: 100%; |
||||
|
justify-content: center; |
||||
|
|
||||
|
.text { |
||||
|
margin-left: 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
&.end { |
||||
|
border-color: #b0b0b0; |
||||
|
background: #cccccc; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.ping { |
||||
|
animation: ping 0.8s ease-in-out infinite both; |
||||
|
transform-origin: 50% 50%; |
||||
|
} |
||||
|
|
||||
|
@keyframes ping { |
||||
|
0% { |
||||
|
transform: scale(0.2); |
||||
|
opacity: 0.8; |
||||
|
} |
||||
|
80% { |
||||
|
transform: scale(1.2); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
100% { |
||||
|
transform: scale(2.2); |
||||
|
opacity: 0; |
||||
|
} |
||||
|
} |
@ -0,0 +1,71 @@ |
|||||
|
import React from 'react' |
||||
|
import { SplitBox } from '@antv/x6-components' |
||||
|
import { Graph, DomEvent } from '@antv/x6' |
||||
|
import { Sidebar } from './sidebar' |
||||
|
import { GraphToolbar } from './toolbar' |
||||
|
import { createGraph } from './util' |
||||
|
import { Setting } from './settings' |
||||
|
import './index.less' |
||||
|
|
||||
|
export default class FlowChart extends React.Component<{}, FlowChart.State> { |
||||
|
private graph: Graph |
||||
|
private container: HTMLDivElement |
||||
|
|
||||
|
componentDidMount() { |
||||
|
DomEvent.disableContextMenu(this.container) |
||||
|
this.graph = createGraph(this.container) |
||||
|
this.graph.center() |
||||
|
this.setState({ inited: true }) |
||||
|
} |
||||
|
|
||||
|
refContainer = (container: HTMLDivElement) => { |
||||
|
this.container = container |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="flowchart-wrap"> |
||||
|
<SplitBox |
||||
|
split="vertical" |
||||
|
minSize={200} |
||||
|
maxSize={-320} |
||||
|
defaultSize={240} |
||||
|
primary="first" |
||||
|
> |
||||
|
<div className="flowchart-sidebar"> |
||||
|
<div className="flowchart-sidebar-title">iMove</div> |
||||
|
<div className="flowchart-sidebar-title">基础组件</div> |
||||
|
<div className="flowchart-sidebar-content"> |
||||
|
{this.graph && this.state.inited && ( |
||||
|
<Sidebar graph={this.graph} /> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
|
<SplitBox |
||||
|
split="vertical" |
||||
|
minSize={200} |
||||
|
maxSize={-320} |
||||
|
defaultSize={240} |
||||
|
primary="second" |
||||
|
> |
||||
|
<div className="flowchart-main"> |
||||
|
<div className="flowchart-toolbar"> |
||||
|
{this.graph && <GraphToolbar graph={this.graph} />} |
||||
|
</div> |
||||
|
<div className="flowchart-graph"> |
||||
|
<div className="graph" ref={this.refContainer} tabIndex={-1} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
{this.graph && this.state.inited && <Setting graph={this.graph} />} |
||||
|
</SplitBox> |
||||
|
</SplitBox> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export namespace FlowChart { |
||||
|
export interface State { |
||||
|
inited: boolean |
||||
|
} |
||||
|
} |
@ -0,0 +1,70 @@ |
|||||
|
import * as React from "react"; |
||||
|
import { Select, Input } from "antd"; |
||||
|
|
||||
|
const { Option } = Select; |
||||
|
|
||||
|
class ExecuteSetting extends React.Component< |
||||
|
ExecuteSetting.Props, |
||||
|
ExecuteSetting.State |
||||
|
> { |
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="execute-setting"> |
||||
|
<label> |
||||
|
<div>事件名称</div> |
||||
|
<Select |
||||
|
defaultValue={this.props.data.title} |
||||
|
style={{ width: 120 }} |
||||
|
onChange={this.props.onTitleChange} |
||||
|
> |
||||
|
<Option value="empty">empty</Option> |
||||
|
<Option value="resolve">resolve</Option> |
||||
|
<Option value="reject">reject</Option> |
||||
|
</Select> |
||||
|
</label> |
||||
|
<label> |
||||
|
<div>action</div> |
||||
|
<Select |
||||
|
defaultValue={this.props.data.action} |
||||
|
style={{ width: 120 }} |
||||
|
onChange={this.props.onActionChange} |
||||
|
> |
||||
|
<Option value="empty">context赋值</Option> |
||||
|
</Select> |
||||
|
</label> |
||||
|
<label> |
||||
|
<div><b>context key</b></div> |
||||
|
<Input |
||||
|
defaultValue={this.props.data.contextKey} |
||||
|
type="text" |
||||
|
allowClear={true} |
||||
|
onBlur={this.props.onKeyChange} |
||||
|
/> |
||||
|
</label> |
||||
|
<label> |
||||
|
<div><b>value path</b></div> |
||||
|
<Input |
||||
|
defaultValue={this.props.data.contextValue} |
||||
|
type="text" |
||||
|
allowClear={true} |
||||
|
onBlur={this.props.onValueChange} |
||||
|
/> |
||||
|
</label> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default ExecuteSetting; |
||||
|
|
||||
|
export namespace ExecuteSetting { |
||||
|
export interface Props { |
||||
|
data: any; |
||||
|
onTitleChange: (e: any) => void; |
||||
|
onActionChange: (action: string) => void; |
||||
|
onKeyChange: (e: any) => void; |
||||
|
onValueChange: (e: any) => void; |
||||
|
} |
||||
|
|
||||
|
export interface State {} |
||||
|
} |
@ -0,0 +1,138 @@ |
|||||
|
import React from "react"; |
||||
|
import { Graph, Cell } from "@antv/x6"; |
||||
|
import StateSetting from "./state"; |
||||
|
import ExecuteSetting from "./execute"; |
||||
|
|
||||
|
export class Setting extends React.Component<Setting.Props, Setting.State> { |
||||
|
constructor(props: Setting.Props) { |
||||
|
super(props); |
||||
|
this.state = this.getNextState(props); |
||||
|
|
||||
|
props.graph.on("selection:changed", () => { |
||||
|
this.setState(this.getNextState()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
getNextState(props: Setting.Props = this.props) { |
||||
|
return { |
||||
|
selectedCells: props.graph.getSelectedCells() |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
onStatusTitleChange = (e: any) => { |
||||
|
const value = e.target.value; |
||||
|
if (value && value.trim()) { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
title: value.trim() |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
onExcuteTitleChange = (title: string) => { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
title |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onSerTypeChange = (serviceType: string) => { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
serviceType |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onActionChange = (action: string) => { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
action |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
onApiChange = (e: any) => { |
||||
|
const value = e.target.value; |
||||
|
if (value && value.trim()) { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
api: value.trim() |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
onKeyChange = (e: any) => { |
||||
|
const value = e.target.value; |
||||
|
if (value && value.trim()) { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
contextKey: value.trim() |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
onValueChange = (e: any) => { |
||||
|
const value = e.target.value; |
||||
|
if (value && value.trim()) { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
this.props.graph.setCellData(cell, { |
||||
|
...data, |
||||
|
contextValue: value.trim() |
||||
|
}); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
render() { |
||||
|
if (this.state.selectedCells.length === 1) { |
||||
|
const cell = this.state.selectedCells[0]; |
||||
|
const data = cell.data; |
||||
|
return ( |
||||
|
<div className="flowchart-format"> |
||||
|
<div className="flowchart-sidebar-title">流程设置</div> |
||||
|
<div className="flowchart-sidebar-content"> |
||||
|
{data && data.type === "status" && ( |
||||
|
<StateSetting |
||||
|
data={data} |
||||
|
onTitleChange={this.onStatusTitleChange} |
||||
|
onApiChange={this.onApiChange} |
||||
|
onSerTypeChange={this.onSerTypeChange} |
||||
|
/> |
||||
|
)} |
||||
|
{data && data.type === "execute" && ( |
||||
|
<ExecuteSetting |
||||
|
data={data} |
||||
|
onTitleChange={this.onExcuteTitleChange} |
||||
|
onActionChange={this.onActionChange} |
||||
|
onKeyChange={this.onKeyChange} |
||||
|
onValueChange={this.onValueChange} |
||||
|
/> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export namespace Setting { |
||||
|
export interface Props { |
||||
|
graph: Graph; |
||||
|
} |
||||
|
|
||||
|
export interface State { |
||||
|
selectedCells: Cell[]; |
||||
|
} |
||||
|
} |
@ -0,0 +1,58 @@ |
|||||
|
import * as React from "react"; |
||||
|
import { Input, Select } from "antd"; |
||||
|
|
||||
|
const { Option } = Select; |
||||
|
|
||||
|
class StateSetting extends React.Component< |
||||
|
StateSetting.Props, |
||||
|
StateSetting.State |
||||
|
> { |
||||
|
render() { |
||||
|
return ( |
||||
|
<div> |
||||
|
<label> |
||||
|
<div>状态名称</div> |
||||
|
<Input |
||||
|
defaultValue={this.props.data.title} |
||||
|
type="text" |
||||
|
allowClear={true} |
||||
|
onBlur={this.props.onTitleChange} |
||||
|
/> |
||||
|
</label> |
||||
|
<label> |
||||
|
<div>服务类型</div> |
||||
|
<Select |
||||
|
defaultValue={this.props.data.serviceType} |
||||
|
style={{ width: 120 }} |
||||
|
onChange={this.props.onSerTypeChange} |
||||
|
> |
||||
|
<Option value="none">none</Option> |
||||
|
<Option value="invoke">invoke</Option> |
||||
|
</Select> |
||||
|
</label> |
||||
|
<label> |
||||
|
<div>api</div> |
||||
|
<Input |
||||
|
defaultValue={this.props.data.api} |
||||
|
type="text" |
||||
|
allowClear={true} |
||||
|
onBlur={this.props.onApiChange} |
||||
|
/> |
||||
|
</label> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default StateSetting; |
||||
|
|
||||
|
export namespace StateSetting { |
||||
|
export interface Props { |
||||
|
data: any; |
||||
|
onTitleChange: (e: any) => void; |
||||
|
onApiChange: (e: any) => void; |
||||
|
onSerTypeChange: (serviceType: string) => void; |
||||
|
} |
||||
|
|
||||
|
export interface State {} |
||||
|
} |
@ -0,0 +1,112 @@ |
|||||
|
import React from "react"; |
||||
|
import classNames from "classnames"; |
||||
|
import { Icon } from "@antv/x6-components"; |
||||
|
import { Graph, Dnd } from "@antv/x6"; |
||||
|
import { data, DataItem, isGroup } from "./data"; |
||||
|
import { addNode } from "./util"; |
||||
|
|
||||
|
export class Sidebar extends React.Component<Sidebar.Props> { |
||||
|
private container: HTMLDivElement; |
||||
|
|
||||
|
refContainer = (container: HTMLDivElement) => { |
||||
|
this.container = container; |
||||
|
}; |
||||
|
|
||||
|
componentDidMount() { |
||||
|
this.container.childNodes.forEach((elem: HTMLElement) => { |
||||
|
const type = elem.getAttribute("data-type") || ""; |
||||
|
const itemData = data.map[type]; |
||||
|
|
||||
|
const dnd = new Dnd<DataItem>(elem, { |
||||
|
data: itemData, |
||||
|
getGraph: () => this.props.graph, |
||||
|
getTargetCell: ({ graph, graphX, graphY }) => { |
||||
|
const cell = graph.getCellAt(graphX, graphY); |
||||
|
if (cell != null && cell.data != null && isGroup(cell.data.type)) { |
||||
|
return cell; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
createDragElement: ({ element }) => { |
||||
|
const elem = document.createElement("div") as HTMLDivElement; |
||||
|
const w = element.offsetWidth; |
||||
|
const h = element.offsetHeight; |
||||
|
elem.style.width = `${w}px`; |
||||
|
elem.style.height = `${h}px`; |
||||
|
elem.style.border = "1px dashed #000"; |
||||
|
elem.style.cursor = "move"; |
||||
|
return elem; |
||||
|
}, |
||||
|
createPreviewElement: ({ graph, element }) => { |
||||
|
const elem = document.createElement("div") as HTMLDivElement; |
||||
|
const w = element.offsetWidth; |
||||
|
const h = element.offsetHeight; |
||||
|
const s = graph.view.scale; |
||||
|
elem.style.width = `${w * s}px`; |
||||
|
elem.style.height = `${h * s}px`; |
||||
|
elem.style.border = "1px dashed #000"; |
||||
|
elem.style.cursor = "move"; |
||||
|
return elem; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
dnd.on("dragPrepare", ({ dragElement }) => { |
||||
|
dragElement.style.margin = "0"; |
||||
|
}); |
||||
|
|
||||
|
dnd.on("drop", ({ data, graph, targetCell, targetPosition }) => { |
||||
|
if (data != null) { |
||||
|
const group = isGroup(data.data.type); |
||||
|
const parent = targetCell || undefined; |
||||
|
let x = targetPosition.x; |
||||
|
let y = targetPosition.y; |
||||
|
// relative parent position
|
||||
|
if (parent != null) { |
||||
|
const geom = parent.getGeometry()!; |
||||
|
x -= geom.bounds.x; |
||||
|
y -= geom.bounds.y; |
||||
|
} |
||||
|
|
||||
|
const cell = addNode( |
||||
|
graph, |
||||
|
data, |
||||
|
x, |
||||
|
y, |
||||
|
group ? 200 : null, |
||||
|
group ? 160 : null, |
||||
|
null, |
||||
|
parent |
||||
|
); |
||||
|
|
||||
|
graph.unSelectCells(graph.getSelectedCells()).selectCell(cell); |
||||
|
} |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="flowchart-source-nodes" ref={this.refContainer}> |
||||
|
{data.items.map(({ data, width, height }) => ( |
||||
|
<div |
||||
|
key={data.type} |
||||
|
data-type={data.type} |
||||
|
style={{ width, height }} |
||||
|
className={classNames("flowchart-node-wrap", data.type)} |
||||
|
> |
||||
|
<div className={classNames("flowchart-node", data.type)}> |
||||
|
{data.icon && <Icon type={data.icon} svg={true} />} |
||||
|
<span className="text">{data.title}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export namespace Sidebar { |
||||
|
export interface Props { |
||||
|
graph: Graph; |
||||
|
} |
||||
|
} |
@ -0,0 +1,250 @@ |
|||||
|
import React from "react"; |
||||
|
import { Toolbar, Menu } from "@antv/x6-components"; |
||||
|
import { Graph, UndoManager } from "@antv/x6"; |
||||
|
|
||||
|
export class GraphToolbar extends React.Component< |
||||
|
GraphToolbar.Props, |
||||
|
GraphToolbar.State |
||||
|
> { |
||||
|
private graph: Graph; |
||||
|
private undoManager: UndoManager; |
||||
|
private commands: GraphToolbar.Command[][]; |
||||
|
|
||||
|
constructor(props: GraphToolbar.Props) { |
||||
|
super(props); |
||||
|
|
||||
|
this.graph = props.graph; |
||||
|
this.undoManager = UndoManager.create(props.graph); |
||||
|
this.commands = [ |
||||
|
[ |
||||
|
{ |
||||
|
name: "zoomIn", |
||||
|
icon: "zoom-in", |
||||
|
tooltip: "放大", |
||||
|
handler: () => { |
||||
|
let scale = this.graph.view.scale; |
||||
|
if (scale >= 8) { |
||||
|
scale += 8; |
||||
|
} else if (scale >= 4) { |
||||
|
scale += 4; |
||||
|
} else if (scale >= 2) { |
||||
|
scale += 1; |
||||
|
} else if (scale >= 1.5) { |
||||
|
scale += 0.5; |
||||
|
} else if (scale >= 1) { |
||||
|
scale += 0.25; |
||||
|
} else if (scale >= 0.7) { |
||||
|
scale += 0.15; |
||||
|
} else if (scale >= 0.4) { |
||||
|
scale += 0.1; |
||||
|
} else if (scale >= 0.15) { |
||||
|
scale += 0.05; |
||||
|
} else if (scale >= 0.01) { |
||||
|
scale += 0.01; |
||||
|
} |
||||
|
this.graph.zoomTo(scale); |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
name: "zoomOut", |
||||
|
icon: "zoom-out", |
||||
|
tooltip: "缩小", |
||||
|
handler: () => { |
||||
|
let scale = this.graph.view.scale; |
||||
|
if (scale <= 0.15) { |
||||
|
scale -= 0.01; |
||||
|
} else if (scale <= 0.4) { |
||||
|
scale -= 0.05; |
||||
|
} else if (scale <= 0.7) { |
||||
|
scale -= 0.1; |
||||
|
} else if (scale <= 1) { |
||||
|
scale -= 0.15; |
||||
|
} else if (scale <= 1.5) { |
||||
|
scale -= 0.25; |
||||
|
} else if (scale <= 2) { |
||||
|
scale -= 0.5; |
||||
|
} else if (scale <= 4) { |
||||
|
scale -= 1; |
||||
|
} else if (scale <= 8) { |
||||
|
scale -= 4; |
||||
|
} else if (scale <= 16) { |
||||
|
scale -= 8; |
||||
|
} |
||||
|
this.graph.zoomTo(scale); |
||||
|
} |
||||
|
} |
||||
|
], |
||||
|
[ |
||||
|
{ |
||||
|
name: "undo", |
||||
|
icon: "undo", |
||||
|
tooltip: "撤销", |
||||
|
shortcut: "Cmd + Z", |
||||
|
handler: () => this.undoManager.undo() |
||||
|
}, |
||||
|
{ |
||||
|
name: "redo", |
||||
|
icon: "redo", |
||||
|
tooltip: "重做", |
||||
|
shortcut: "Cmd + Shift + Z", |
||||
|
handler: () => this.undoManager.redo() |
||||
|
} |
||||
|
], |
||||
|
[ |
||||
|
{ |
||||
|
name: "delete", |
||||
|
icon: "delete", |
||||
|
tooltip: "删除", |
||||
|
shortcut: "Delete", |
||||
|
handler: () => this.graph.deleteCells() |
||||
|
}, |
||||
|
{ |
||||
|
name: "save", |
||||
|
icon: "save", |
||||
|
tooltip: "保存", |
||||
|
shortcut: "Cmd + S", |
||||
|
handler: () => { |
||||
|
const graphData = this.graph.toJSON(); |
||||
|
console.log(JSON.stringify(graphData)); |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
]; |
||||
|
|
||||
|
this.commands.forEach(items => |
||||
|
items.forEach(item => { |
||||
|
if (item.shortcut) { |
||||
|
if (item.shortcut === "Delete") { |
||||
|
this.graph.bindKey(["del", "backspace"], item.handler); |
||||
|
} else { |
||||
|
this.graph.bindKey(item.shortcut, item.handler); |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
); |
||||
|
|
||||
|
this.graph.on("selection:changed", this.updateState); |
||||
|
this.graph.view.on("scale", this.updateState); |
||||
|
this.graph.view.on("scaleAndTranslate", this.updateState); |
||||
|
this.graph.model.on("change", this.updateState); |
||||
|
this.undoManager.on("undo", this.updateState); |
||||
|
this.undoManager.on("redo", this.updateState); |
||||
|
|
||||
|
this.state = this.getNextState(); |
||||
|
} |
||||
|
|
||||
|
updateState = () => { |
||||
|
this.setState(this.getNextState()); |
||||
|
}; |
||||
|
|
||||
|
getNextState() { |
||||
|
return { |
||||
|
zoomIn: this.graph.view.scale === this.graph.maxScale, |
||||
|
zoomOut: this.graph.view.scale === this.graph.minScale, |
||||
|
undo: !this.undoManager.canUndo(), |
||||
|
redo: !this.undoManager.canRedo(), |
||||
|
delete: this.graph.getSelectedCells().length <= 0 |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
resetZoom = () => { |
||||
|
if (this.graph.view.scale !== 1) { |
||||
|
this.graph.zoomTo(1); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
onClick = (name: string) => { |
||||
|
if (name === "resetView") { |
||||
|
this.graph.zoomTo(1); |
||||
|
this.graph.center(); |
||||
|
} else if (name === "fitWindow") { |
||||
|
this.graph.fit(8); |
||||
|
// this.graph.center()
|
||||
|
} else if ( |
||||
|
name === "25" || |
||||
|
name === "50" || |
||||
|
name === "75" || |
||||
|
name === "100" || |
||||
|
name === "125" || |
||||
|
name === "150" || |
||||
|
name === "200" || |
||||
|
name === "400" |
||||
|
) { |
||||
|
const scale = parseInt(name, 10) / 100; |
||||
|
this.graph.zoomTo(scale); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
renderZoomDropdown() { |
||||
|
const MenuItem = Menu.Item; |
||||
|
return ( |
||||
|
<Menu hasIcon={false}> |
||||
|
<MenuItem name="25">25%</MenuItem> |
||||
|
<MenuItem name="50">50%</MenuItem> |
||||
|
<MenuItem name="75">75%</MenuItem> |
||||
|
<MenuItem name="100">100%</MenuItem> |
||||
|
<MenuItem name="125">125%</MenuItem> |
||||
|
<MenuItem name="150">150%</MenuItem> |
||||
|
<MenuItem name="200">200%</MenuItem> |
||||
|
<MenuItem name="400">400%</MenuItem> |
||||
|
</Menu> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const Item = Toolbar.Item; |
||||
|
const Group = Toolbar.Group; |
||||
|
|
||||
|
return ( |
||||
|
<Toolbar onClick={this.onClick} hoverEffect={true} align="left"> |
||||
|
<Item dropdown={this.renderZoomDropdown()}> |
||||
|
{(this.graph.view.scale * 100).toFixed(0)}% |
||||
|
</Item> |
||||
|
<Group> |
||||
|
<Item name="resetView" tooltip="重置视口" icon="retweet" /> |
||||
|
<Item name="fitWindow" tooltip="适应窗口" icon="fullscreen" /> |
||||
|
</Group> |
||||
|
{this.commands.map(items => ( |
||||
|
<Group key={items.map(i => i.name).join("-")}> |
||||
|
{items.map(item => ( |
||||
|
<Item |
||||
|
key={item.name} |
||||
|
name={item.name} |
||||
|
icon={item.icon} |
||||
|
tooltip={ |
||||
|
item.shortcut |
||||
|
? `${item.tooltip}(${item.shortcut})` |
||||
|
: item.tooltip |
||||
|
} |
||||
|
disabled={(this.state as any)[item.name] as boolean} |
||||
|
onClick={item.handler} |
||||
|
/> |
||||
|
))} |
||||
|
</Group> |
||||
|
))} |
||||
|
</Toolbar> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export namespace GraphToolbar { |
||||
|
export interface Props { |
||||
|
graph: Graph; |
||||
|
} |
||||
|
|
||||
|
export interface State { |
||||
|
zoomIn: boolean; |
||||
|
zoomOut: boolean; |
||||
|
redo: boolean; |
||||
|
undo: boolean; |
||||
|
delete: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface Command { |
||||
|
name: string; |
||||
|
icon: string; |
||||
|
tooltip: string; |
||||
|
shortcut?: string; |
||||
|
handler: () => void; |
||||
|
} |
||||
|
} |
@ -0,0 +1,185 @@ |
|||||
|
import { Cell, Graph, Style } from "@antv/x6"; |
||||
|
import { DataItem, isCircle, isGroup, mockGraphData } from "./data"; |
||||
|
|
||||
|
export function createGraph(container: HTMLDivElement) { |
||||
|
return new Graph(container, { |
||||
|
rotate: false, |
||||
|
resize: true, |
||||
|
folding: false, |
||||
|
infinite: true, // 无限大画布
|
||||
|
// pageVisible: true,
|
||||
|
// pageBreak: {
|
||||
|
// enabled: true,
|
||||
|
// dsahed: true,
|
||||
|
// stroke: '#c0c0c0',
|
||||
|
// },
|
||||
|
// pageFormat: {
|
||||
|
// width: 800,
|
||||
|
// height: 960,
|
||||
|
// },
|
||||
|
// mouseWheel: true,
|
||||
|
rubberband: true, |
||||
|
allowLoops: true, |
||||
|
backgroundColor: "#f8f9fa", |
||||
|
grid: { |
||||
|
type: "dot", |
||||
|
color: "#bcbcbc" |
||||
|
}, |
||||
|
guide: { |
||||
|
enabled: true, |
||||
|
dashed: true, |
||||
|
stroke: "#ff5500" |
||||
|
}, |
||||
|
connection: { |
||||
|
enabled: true, |
||||
|
hotspotable: false, |
||||
|
livePreview: true, |
||||
|
createEdge(options) { |
||||
|
const style = options.style; |
||||
|
fixEdgeStyle(style, style); |
||||
|
return this.createEdge(options); |
||||
|
} |
||||
|
}, |
||||
|
connectionPreview: { |
||||
|
stroke: "#1890ff" |
||||
|
}, |
||||
|
connectionHighlight: {}, |
||||
|
allowDanglingEdges: false, |
||||
|
keyboard: { |
||||
|
enabled: true, |
||||
|
global: false, |
||||
|
escape: true |
||||
|
}, |
||||
|
selectionPreview: { |
||||
|
dashed: false, |
||||
|
strokeWidth: 2 |
||||
|
}, |
||||
|
nodeStyle: { |
||||
|
fill: "rgba(0, 0, 0, 0)", |
||||
|
stroke: "none", |
||||
|
label: false, |
||||
|
editable: false |
||||
|
}, |
||||
|
edgeStyle: { |
||||
|
edge: "elbow", |
||||
|
elbow: "vertical", |
||||
|
labelBackgroundColor: "#f8f9fa", |
||||
|
rounded: true, |
||||
|
movable: false |
||||
|
}, |
||||
|
dropEnabled: true, |
||||
|
dropTargetHighlight: { |
||||
|
stroke: "#87d068", |
||||
|
opacity: 1 |
||||
|
}, |
||||
|
anchor: { |
||||
|
inductiveSize: 24 |
||||
|
}, |
||||
|
// anchorTip: {
|
||||
|
// enabled: true,
|
||||
|
// opacity: 1,
|
||||
|
// className: 'ping',
|
||||
|
// },
|
||||
|
anchorHighlight: { |
||||
|
strokeWidth: 16 |
||||
|
}, |
||||
|
isValidDropTarget(target) { |
||||
|
if (target && target.data) { |
||||
|
return isGroup(target.data.type); |
||||
|
} |
||||
|
}, |
||||
|
isLabelMovable() { |
||||
|
return false; |
||||
|
}, |
||||
|
shouldRedrawOnDataChange() { |
||||
|
return true; |
||||
|
}, |
||||
|
getAnchors(cell) { |
||||
|
if (cell != null && this.model.isNode(cell)) { |
||||
|
const type = cell.data.type; |
||||
|
if (type === "start") { |
||||
|
return [[0.5, 1]]; |
||||
|
} |
||||
|
|
||||
|
if (isGroup(type)) { |
||||
|
return [ |
||||
|
[0.25, 0], |
||||
|
[0.5, 0], |
||||
|
[0.75, 0], |
||||
|
|
||||
|
[0, 0.25], |
||||
|
[0, 0.5], |
||||
|
[0, 0.75], |
||||
|
|
||||
|
[1, 0.25], |
||||
|
[1, 0.5], |
||||
|
[1, 0.75], |
||||
|
|
||||
|
[0.25, 1], |
||||
|
[0.5, 1], |
||||
|
[0.75, 1] |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
return [[0.5, 0], [0, 0.5], [1, 0.5], [0.5, 1]]; |
||||
|
} |
||||
|
return null; |
||||
|
}, |
||||
|
getHtml(cell) { |
||||
|
const data = cell.data; |
||||
|
const group = isGroup(data.type); |
||||
|
return ` |
||||
|
<div class="flowchart-node${group ? " is-gruop" : ""} ${data.type}"> |
||||
|
${data.icon ? `<i class="icon">${data.icon}</i>` : ""} |
||||
|
${group ? "" : `<span class="text">${data.title}</span>`} |
||||
|
</div> |
||||
|
`;
|
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
export function addNode( |
||||
|
graph: Graph, |
||||
|
item: DataItem, |
||||
|
x: number, |
||||
|
y: number, |
||||
|
width?: number | null, |
||||
|
height?: number | null, |
||||
|
title?: string | null, |
||||
|
parent?: Cell |
||||
|
) { |
||||
|
const data = { ...item.data }; |
||||
|
if (title) { |
||||
|
data.title = title; |
||||
|
} |
||||
|
|
||||
|
return graph.addNode({ |
||||
|
x, |
||||
|
y, |
||||
|
data, |
||||
|
parent, |
||||
|
width: width || item.width, |
||||
|
height: height || item.height, |
||||
|
shape: "html", |
||||
|
resizable: !isCircle(item), |
||||
|
perimeter: isCircle(item) ? "ellipse" : "rectangle" |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function fixEdgeStyle(raw: Style, result: Style) { |
||||
|
if (raw.targetAnchorX === 1 || raw.targetAnchorX === 0) { |
||||
|
result.elbow = "horizontal"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function addEdge(graph: Graph, options: any) { |
||||
|
const style: Style = {}; |
||||
|
fixEdgeStyle(options, style); |
||||
|
graph.addEdge({ ...options, style }); |
||||
|
} |
||||
|
|
||||
|
// export function graph2flow(graphJson: any) {
|
||||
|
// const { nodes, edges } = mockGraphData;
|
||||
|
|
||||
|
// nodes.forEach()
|
||||
|
// }
|
@ -0,0 +1,6 @@ |
|||||
|
import React from "react"; |
||||
|
import FlowChart from "./flowchart"; |
||||
|
|
||||
|
export default function() { |
||||
|
return <FlowChart />; |
||||
|
} |
@ -0,0 +1,44 @@ |
|||||
|
export function randomColor() { |
||||
|
var letters = "0123456789ABCDEF"; |
||||
|
var color = "#"; |
||||
|
for (var i = 0; i < 6; i += 1) { |
||||
|
color += letters[Math.floor(Math.random() * 16)]; |
||||
|
} |
||||
|
return color; |
||||
|
} |
||||
|
|
||||
|
export function invertColor(hex: string, bw: boolean) { |
||||
|
if (hex.indexOf("#") === 0) { |
||||
|
hex = hex.slice(1); |
||||
|
} |
||||
|
// convert 3-digit hex to 6-digits.
|
||||
|
if (hex.length === 3) { |
||||
|
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; |
||||
|
} |
||||
|
|
||||
|
if (hex.length !== 6) { |
||||
|
throw new Error("Invalid HEX color."); |
||||
|
} |
||||
|
|
||||
|
const r = parseInt(hex.slice(0, 2), 16); |
||||
|
const g = parseInt(hex.slice(2, 4), 16); |
||||
|
const b = parseInt(hex.slice(4, 6), 16); |
||||
|
|
||||
|
if (bw) { |
||||
|
// http://stackoverflow.com/a/3943023/112731
|
||||
|
return r * 0.299 + g * 0.587 + b * 0.114 > 186 ? "#000000" : "#FFFFFF"; |
||||
|
} |
||||
|
|
||||
|
// invert color components
|
||||
|
const rr = (255 - r).toString(16); |
||||
|
const gg = (255 - g).toString(16); |
||||
|
const bb = (255 - b).toString(16); |
||||
|
|
||||
|
// pad each with zeros and return
|
||||
|
return "#" + padZero(rr) + padZero(gg) + padZero(bb); |
||||
|
} |
||||
|
|
||||
|
function padZero(str: string, len: number = 2) { |
||||
|
var zeros = new Array(len).join("0"); |
||||
|
return (zeros + str).slice(-len); |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "esnext", |
||||
|
"module": "esnext", |
||||
|
"moduleResolution": "node", |
||||
|
"jsx": "react", |
||||
|
"baseUrl": ".", |
||||
|
"importHelpers": true, |
||||
|
"sourceMap": true, |
||||
|
"esModuleInterop": true, |
||||
|
"noImplicitAny": true, |
||||
|
"noEmitOnError": true, |
||||
|
"noUnusedLocals": true, |
||||
|
"strictNullChecks": true, |
||||
|
"resolveJsonModule": true, |
||||
|
"experimentalDecorators": true, |
||||
|
"strictPropertyInitialization": false, |
||||
|
"allowSyntheticDefaultImports": true, |
||||
|
"paths": { |
||||
|
"@/*": [ |
||||
|
"src/*" |
||||
|
] |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,25 @@ |
|||||
|
{ |
||||
|
"extends": ["tslint-react", "tslint-eslint-rules"], |
||||
|
"rules": { |
||||
|
"semicolon": [true, "never"], |
||||
|
"quotemark": [true, "single", "jsx-double"], |
||||
|
"trailing-comma": [ |
||||
|
true, |
||||
|
{ |
||||
|
"singleline": "never", |
||||
|
"multiline": { |
||||
|
"objects": "always", |
||||
|
"arrays": "always", |
||||
|
"functions": "always", |
||||
|
"typeLiterals": "ignore" |
||||
|
}, |
||||
|
"esSpecCompliant": true |
||||
|
} |
||||
|
], |
||||
|
"no-construct": true, |
||||
|
"no-debugger": true, |
||||
|
"no-reference": true, |
||||
|
"jsx-no-multiline-js": false, |
||||
|
"jsx-no-lambda": false |
||||
|
} |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
declare module '*.less' |
Loading…
Reference in new issue