i5ting
4 years ago
45 changed files with 1070 additions and 285 deletions
@ -1,78 +0,0 @@ |
|||||
# iMove |
|
||||
|
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section --> |
|
||||
[![All Contributors](https://img.shields.io/badge/all_contributors-0-orange.svg?style=flat-square)](#contributors-) |
|
||||
<!-- ALL-CONTRIBUTORS-BADGE:END --> |
|
||||
|
|
||||
<div align="center"> |
|
||||
<img src="https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=3874775950,1064987171&fm=26&gp=0.jpg" width="200px"> |
|
||||
</div> |
|
||||
|
|
||||
<p align="center"> |
|
||||
iMove 是一个逻辑可复用的,面向函数的,流程可视化的 JavaScript 工具库。 |
|
||||
</p> |
|
||||
|
|
||||
[English](./README.en-US.md) | 简体中文 |
|
||||
|
|
||||
## 特性 |
|
||||
|
|
||||
- [x] **流程可视化**: 上手简单,绘图方便,逻辑表达更直观,易于理解 |
|
||||
- [x] **逻辑复用**: iMove 节点支持复用,单节点支持参数配置 |
|
||||
- [x] **灵活可扩展**: 仅需写一个函数,节点可扩展,支持插件集成 |
|
||||
- [ ] **多语言编译**: 无语言编译出码限制(例: 支持 JavaScript, Java 编译出码) |
|
||||
|
|
||||
## 快速开始 |
|
||||
|
|
||||
### 步骤 1. 准备 |
|
||||
|
|
||||
下载仓库,安装并启动 |
|
||||
|
|
||||
```bash |
|
||||
$ git clone https://github.com/imgcook/imove.git |
|
||||
$ cd ./imove/example |
|
||||
$ npm install |
|
||||
$ npm start |
|
||||
``` |
|
||||
|
|
||||
### 步骤 2. 绘制流程图 |
|
||||
|
|
||||
从左侧拖动节点至中央画布,绘制流程图 |
|
||||
|
|
||||
![flowchart](https://img.alicdn.com/tfs/TB1aoYe4pP7gK0jSZFjXXc5aXXa-3090-1806.jpg) |
|
||||
|
|
||||
### 步骤 3. 配置节点 |
|
||||
|
|
||||
选择节点,修改节点名,编辑节点代码 |
|
||||
|
|
||||
![flowchart-usage1](https://img.alicdn.com/tfs/TB1z6DKoZieb18jSZFvXXaI3FXa-1924-1125.png) |
|
||||
|
|
||||
![flowchart-usage2](https://img.alicdn.com/tfs/TB1lC26tTM11u4jSZPxXXahcXXa-1924-1125.png) |
|
||||
|
|
||||
## 贡献 |
|
||||
|
|
||||
1. Fork 仓库 |
|
||||
2. 创建分支 (`git checkout -b my-new-feature`) |
|
||||
3. 提交修改 (`git commit -am 'Add some feature'`) |
|
||||
4. 推送 (`git push origin my-new-feature`) |
|
||||
5. 创建 PR |
|
||||
|
|
||||
## 欢迎fork和反馈 |
|
||||
|
|
||||
如有建议或意见,欢迎在 github [issues](https://github.com/imgcook/imove/issues) 区提问 |
|
||||
|
|
||||
## 协议 |
|
||||
|
|
||||
本仓库遵循 [MIT 协议](http://www.opensource.org/licenses/MIT) |
|
||||
|
|
||||
## 贡献者 ✨ |
|
||||
|
|
||||
感谢所有贡献的人 ([emoji key](https://allcontributors.org/docs/en/emoji-key)): |
|
||||
|
|
||||
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> |
|
||||
<!-- prettier-ignore-start --> |
|
||||
<!-- markdownlint-disable --> |
|
||||
<!-- markdownlint-restore --> |
|
||||
<!-- prettier-ignore-end --> |
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END --> |
|
||||
|
|
||||
本仓库遵循 [all-contributors](https://github.com/all-contributors/all-contributors) 规范,欢迎贡献! |
|
@ -0,0 +1 @@ |
|||||
|
{} |
@ -1,28 +0,0 @@ |
|||||
/// <reference types="cypress" />
|
|
||||
|
|
||||
context('Actions', () => { |
|
||||
// 1. 打开http://localhost:8001/
|
|
||||
beforeEach(() => { |
|
||||
cy.visit('http://localhost:8001/') |
|
||||
}) |
|
||||
|
|
||||
// 2. 测试是否能将左侧的组件拖动到画布中
|
|
||||
it('the components on the left can be dragged into the canvas', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 3. 测试是否可修改节点的显示名称、逻辑触发名称、代码、投放配置schema、依赖
|
|
||||
it('you can modify the display name, logical trigger name, code, delivery configuration schema, and dependencies of the node', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 4. 测试能够run起来一个最小流程(写好代码)
|
|
||||
it('A minimal process can be run', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 5. 测试更改节点位置,输出结果不变
|
|
||||
it('ensure that the node position is changed and the output result remains unchanged', () => { |
|
||||
|
|
||||
}) |
|
||||
}) |
|
@ -0,0 +1,45 @@ |
|||||
|
/// <reference types="cypress" />
|
||||
|
|
||||
|
context('Actions', () => { |
||||
|
beforeEach(() => { |
||||
|
cy.visit('http://localhost:8000/') |
||||
|
cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
function moveNodeAndSelect() { |
||||
|
cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(0) |
||||
|
.trigger('mousedown', { which: 1, force: true }) |
||||
|
.trigger('mousemove', { clientX: 400, clientY: 300, force: true }) |
||||
|
.trigger('mouseup', { force: true }) |
||||
|
cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(1).click({ force: true }) // 点击移动到画布中的节点
|
||||
|
} |
||||
|
|
||||
|
function assertEdit(nth, content) { |
||||
|
moveNodeAndSelect() |
||||
|
const ele = cy.get(`#rc-tabs-0-panel-basic > div > div:nth-child(${nth}) > button`).eq(0) |
||||
|
ele.should('have.text', '编 辑') |
||||
|
ele.click({ force: true }) |
||||
|
cy.get('.ant-modal-content').should('exist') |
||||
|
cy.get('.ace_content').type(content) |
||||
|
cy.get('button.ant-btn.ant-btn-primary').eq(0).click({ force: true }) |
||||
|
ele.click({ force: true }) |
||||
|
cy.get('.ace_content').should('contain', content) |
||||
|
} |
||||
|
|
||||
|
it('修改节点名称', () => { |
||||
|
moveNodeAndSelect() |
||||
|
const ele = cy.get('#rc-tabs-0-panel-basic > div > div:nth-child(1) > input').eq(0) |
||||
|
ele.clear({ force: true }) |
||||
|
ele.type('新的开始') |
||||
|
ele.should('have.value', '新的开始') |
||||
|
}) |
||||
|
it('修改节点代码', () => { |
||||
|
assertEdit(2, 'console.log(1)') |
||||
|
}) |
||||
|
it('修改投放配置schema', () => { |
||||
|
assertEdit(3, `"a":1`) |
||||
|
}) |
||||
|
it('修改npm依赖', () => { |
||||
|
assertEdit(4, `"@ant-design/icons": "^4.0.6"`) |
||||
|
}) |
||||
|
}) |
@ -1,28 +0,0 @@ |
|||||
/// <reference types="cypress" />
|
|
||||
|
|
||||
context('Actions', () => { |
|
||||
// 1. 打开http://localhost:8001/
|
|
||||
beforeEach(() => { |
|
||||
cy.visit('http://localhost:8001/') |
|
||||
}) |
|
||||
|
|
||||
// 2. 测试是否能将左侧的组件拖动到画布中
|
|
||||
it('the components on the left can be dragged into the canvas', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 3. 测试是否可修改节点的显示名称、逻辑触发名称、代码、投放配置schema、依赖
|
|
||||
it('you can modify the display name, logical trigger name, code, delivery configuration schema, and dependencies of the node', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 4. 测试能够run起来一个最小流程(写好代码)
|
|
||||
it('A minimal process can be run', () => { |
|
||||
|
|
||||
}) |
|
||||
|
|
||||
// 5. 测试更改节点位置,输出结果不变
|
|
||||
it('ensure that the node position is changed and the output result remains unchanged', () => { |
|
||||
|
|
||||
}) |
|
||||
}) |
|
@ -0,0 +1,42 @@ |
|||||
|
/// <reference types="cypress" />
|
||||
|
|
||||
|
context('Actions', () => { |
||||
|
beforeEach(() => { |
||||
|
cy.visit('http://localhost:8000/') |
||||
|
cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
function moveNode(selector, x, y) { |
||||
|
cy.get(selector).eq(0) |
||||
|
.trigger('mousedown', { which: 1, force: true }) |
||||
|
.trigger('mousemove', { clientX: x, clientY: y, force: true }) |
||||
|
.trigger('mouseup', { force: true }) |
||||
|
} |
||||
|
|
||||
|
// 节点/边操作测试
|
||||
|
it('添加 开始/分支/处理 节点', () => { |
||||
|
const DEFAULT_COUNT = 1 |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(1) > circle', 300, 150) |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(2) > polygon', 400, 150) |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(3) > rect', 500, 150) |
||||
|
cy.get('svg> g > g.x6-graph-svg-stage circle').its('length').should('be.gt', DEFAULT_COUNT) |
||||
|
cy.get('svg> g > g.x6-graph-svg-stage polygon').its('length').should('be.gt', DEFAULT_COUNT) |
||||
|
cy.get('svg> g > g.x6-graph-svg-stage rect').its('length').should('be.gt', DEFAULT_COUNT) |
||||
|
}) |
||||
|
|
||||
|
it('边连线 & 删除节点/边', () => { |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(1) > circle', 300, 150) |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(2) > polygon', 400, 150) |
||||
|
moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(3) > rect', 500, 150) |
||||
|
const node1 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(1) |
||||
|
const node2 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(2)').eq(1) |
||||
|
const node3 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(3)').eq(1) |
||||
|
node1.click('right', { force: true }).trigger('mousemove', { clientX: node2.clientX, clientY: node2.clientY, force: true }).trigger('mouseup', { force: true }) |
||||
|
node2.click('right', { force: true }).trigger('mousemove', { clientX: node3.clientX, clientY: node3.clientY, force: true }).trigger('mouseup', { force: true }) |
||||
|
cy.get('svg path:nth-child(1)').should('exist') |
||||
|
cy.get('svg path:nth-child(1)').eq(1).click({ force: true }) |
||||
|
cy.get('body').type('{del}') |
||||
|
cy.get('svg path:nth-child(1)').eq(1).click({ force: true }) |
||||
|
cy.get('body').type('{del}') |
||||
|
}) |
||||
|
}) |
@ -0,0 +1,100 @@ |
|||||
|
/// <reference types="cypress" />
|
||||
|
|
||||
|
// 工具栏测试
|
||||
|
context('Actions', () => { |
||||
|
const toolSelector = (i, j, k) => { |
||||
|
if (k) { |
||||
|
return `.index-module_container__1D841 > :nth-child(${i}) > :nth-child(${j})> :nth-child(${k})` |
||||
|
} else { |
||||
|
return `.index-module_container__1D841 > :nth-child(${i}) > :nth-child(${j})` |
||||
|
} |
||||
|
} |
||||
|
beforeEach(() => { |
||||
|
cy.visit('http://localhost:8000/') |
||||
|
cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true }) |
||||
|
}) |
||||
|
|
||||
|
function moveNode(selector, x, y) { |
||||
|
cy.get(selector).eq(0) |
||||
|
.trigger('mousedown', { which: 1, force: true }) |
||||
|
.trigger('mousemove', { clientX: x, clientY: y, force: true }) |
||||
|
.trigger('mouseup', { force: true }) |
||||
|
} |
||||
|
|
||||
|
// 第一组
|
||||
|
it('保存', () => { |
||||
|
const save = cy.get(toolSelector(1, 1)) |
||||
|
}) |
||||
|
it('适配窗口', () => { |
||||
|
const fitWindow = cy.get(toolSelector(1, 2)) |
||||
|
}) |
||||
|
it('撤销', () => { |
||||
|
const undo = cy.get(toolSelector(1, 3)) |
||||
|
const node = 'svg > g > g.x6-graph-svg-stage > g:nth-child(1)' |
||||
|
moveNode(node, 350, 300) |
||||
|
moveNode(node, 350, 300) |
||||
|
undo.click({ multiple: true, force: true }) |
||||
|
undo.click({ multiple: true, force: true }) |
||||
|
cy.get(node).should('have.length', 2) |
||||
|
}) |
||||
|
it('重做', () => { |
||||
|
const redo = cy.get(toolSelector(1, 4)) |
||||
|
const node = 'svg > g > g.x6-graph-svg-stage > g:nth-child(1)' |
||||
|
moveNode(node, 350, 300) |
||||
|
redo.click() |
||||
|
redo.click() |
||||
|
cy.get(node).should('have.length', 2) |
||||
|
}) |
||||
|
|
||||
|
// 第二组
|
||||
|
it('缩小', () => { |
||||
|
const zoomOut = cy.get(toolSelector(2, 1, 1)) |
||||
|
}) |
||||
|
it('放大', () => { |
||||
|
const zoomIn = cy.get(toolSelector(2, 1, 2)) |
||||
|
}) |
||||
|
|
||||
|
// 第三组
|
||||
|
it('修改字号', () => { |
||||
|
const fontSize = cy.get(toolSelector(3, 1)) |
||||
|
}) |
||||
|
it('修改字重', () => { |
||||
|
const fontWeight = cy.get(toolSelector(3, 2)) |
||||
|
}) |
||||
|
it('修改斜体', () => { |
||||
|
const italic = cy.get(toolSelector(3, 3)) |
||||
|
}) |
||||
|
it('修改下划线', () => { |
||||
|
const underline = cy.get(toolSelector(3, 4)) |
||||
|
}) |
||||
|
|
||||
|
// 第四组
|
||||
|
it('修改文字颜色', () => { |
||||
|
const textColor = cy.get(toolSelector(4, 1)) |
||||
|
}) |
||||
|
it('修改背景颜色', () => { |
||||
|
const bgColor = cy.get(toolSelector(4, 2)) |
||||
|
}) |
||||
|
it('修改边框颜色', () => { |
||||
|
const borderColor = cy.get(toolSelector(4, 3)) |
||||
|
}) |
||||
|
it('修改线条样式', () => { |
||||
|
const lineStyle = cy.get(toolSelector(4, 4)) |
||||
|
}) |
||||
|
|
||||
|
// 第五组
|
||||
|
it('修改水平方向对齐', () => { |
||||
|
const align = cy.get(toolSelector(5, 1)) |
||||
|
}) |
||||
|
it('修改垂直方向对齐', () => { |
||||
|
const vertical = cy.get(toolSelector(5, 2)) |
||||
|
}) |
||||
|
|
||||
|
// 第六组
|
||||
|
it('修改层级置顶', () => { |
||||
|
const top = cy.get(toolSelector(6, 1)) |
||||
|
}) |
||||
|
it('修改层级置底', () => { |
||||
|
const bottom = cy.get(toolSelector(6, 2)) |
||||
|
}) |
||||
|
}) |
@ -1,15 +0,0 @@ |
|||||
# umi project |
|
||||
|
|
||||
## Getting Started |
|
||||
|
|
||||
Install dependencies, |
|
||||
|
|
||||
```bash |
|
||||
$ yarn |
|
||||
``` |
|
||||
|
|
||||
Start the dev server, |
|
||||
|
|
||||
```bash |
|
||||
$ yarn start |
|
||||
``` |
|
@ -0,0 +1,41 @@ |
|||||
|
const path = require('path'); |
||||
|
const low = require('lowdb'); |
||||
|
const FileSync = require('lowdb/adapters/FileSync'); |
||||
|
|
||||
|
const adapter = new FileSync(path.join(__dirname, './db.json')); |
||||
|
const db = low(adapter); |
||||
|
|
||||
|
const queryGraph = (req: any, res: any) => { |
||||
|
const { projectId = 'default' } = req.body; |
||||
|
const data = db.get(projectId).value() || []; |
||||
|
res.send({ status: 200, code: 0, success: true, data: { cells: data } }); |
||||
|
}; |
||||
|
|
||||
|
const modifyGraph = (req: any, res: any) => { |
||||
|
const { projectId = 'default', actions = [] } = req.body; |
||||
|
const projectData = db.get(projectId).value() || []; |
||||
|
actions.forEach((action: any) => { |
||||
|
const { data, actionType } = action; |
||||
|
if (actionType === 'create') { |
||||
|
projectData.push(data); |
||||
|
} else if (actionType === 'update') { |
||||
|
const foundIdx = projectData.findIndex((item: any) => item.id === data.id); |
||||
|
if (foundIdx > -1) { |
||||
|
projectData[foundIdx] = data; |
||||
|
} |
||||
|
} else if (actionType === 'remove') { |
||||
|
const foundIdx = projectData.findIndex((item: any) => item.id === data.id); |
||||
|
if (foundIdx > -1) { |
||||
|
projectData.splice(foundIdx, 1); |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
db.set(projectId, projectData).write(); |
||||
|
res.send({ status: 200, code: 0, success: true, data: [] }); |
||||
|
}; |
||||
|
|
||||
|
|
||||
|
export default { |
||||
|
'POST /api/queryGraph': queryGraph, |
||||
|
'POST /api/modifyGraph': modifyGraph, |
||||
|
}; |
@ -0,0 +1,336 @@ |
|||||
|
{ |
||||
|
"default": [ |
||||
|
{ |
||||
|
"position": { |
||||
|
"x": 420, |
||||
|
"y": 90 |
||||
|
}, |
||||
|
"size": { |
||||
|
"width": 80, |
||||
|
"height": 40 |
||||
|
}, |
||||
|
"shape": "imove-behavior", |
||||
|
"data": { |
||||
|
"label": "处理", |
||||
|
"configSchema": "{\n \n}", |
||||
|
"configData": {}, |
||||
|
"dependencies": "{\n \n}", |
||||
|
"code": "export default async function(ctx) {\n \n}" |
||||
|
}, |
||||
|
"ports": { |
||||
|
"groups": { |
||||
|
"top": { |
||||
|
"position": "top", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"right": { |
||||
|
"position": "right", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"bottom": { |
||||
|
"position": "bottom", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"left": { |
||||
|
"position": "left", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"items": [ |
||||
|
{ |
||||
|
"id": "top", |
||||
|
"group": "top" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "right", |
||||
|
"group": "right" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "bottom", |
||||
|
"group": "bottom" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "left", |
||||
|
"group": "left" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
"id": "81e87610-c749-4667-8382-00c62be30e2b", |
||||
|
"zIndex": 1 |
||||
|
}, |
||||
|
{ |
||||
|
"position": { |
||||
|
"x": 86, |
||||
|
"y": 70 |
||||
|
}, |
||||
|
"size": { |
||||
|
"width": 80, |
||||
|
"height": 80 |
||||
|
}, |
||||
|
"shape": "imove-start", |
||||
|
"data": { |
||||
|
"label": "开始", |
||||
|
"configSchema": "{\n \n}", |
||||
|
"configData": {}, |
||||
|
"trigger": "start", |
||||
|
"dependencies": "{\n \n}", |
||||
|
"code": "export default async function(ctx) {\n \n}" |
||||
|
}, |
||||
|
"ports": { |
||||
|
"groups": { |
||||
|
"top": { |
||||
|
"position": "top", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"right": { |
||||
|
"position": "right", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"bottom": { |
||||
|
"position": "bottom", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"left": { |
||||
|
"position": "left", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"items": [ |
||||
|
{ |
||||
|
"id": "top", |
||||
|
"group": "top" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "right", |
||||
|
"group": "right" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "bottom", |
||||
|
"group": "bottom" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "left", |
||||
|
"group": "left" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
"id": "de868d18-9ec0-4dac-abbe-5cb9e3c20e2f", |
||||
|
"zIndex": 2 |
||||
|
}, |
||||
|
{ |
||||
|
"position": { |
||||
|
"x": 246, |
||||
|
"y": 90 |
||||
|
}, |
||||
|
"size": { |
||||
|
"width": 80, |
||||
|
"height": 40 |
||||
|
}, |
||||
|
"shape": "imove-branch", |
||||
|
"data": { |
||||
|
"label": "判断", |
||||
|
"configSchema": "{\n \n}", |
||||
|
"configData": {}, |
||||
|
"dependencies": "{\n \n}", |
||||
|
"ports": { |
||||
|
"right": { |
||||
|
"condition": "true" |
||||
|
}, |
||||
|
"bottom": { |
||||
|
"condition": "false" |
||||
|
} |
||||
|
}, |
||||
|
"code": "export default async function(ctx) {\n return true;\n}" |
||||
|
}, |
||||
|
"ports": { |
||||
|
"groups": { |
||||
|
"top": { |
||||
|
"position": "top", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"right": { |
||||
|
"zIndex": 999999999, |
||||
|
"position": "right", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
}, |
||||
|
"label": { |
||||
|
"position": "right" |
||||
|
} |
||||
|
}, |
||||
|
"bottom": { |
||||
|
"zIndex": 999999999, |
||||
|
"position": "bottom", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
}, |
||||
|
"label": { |
||||
|
"position": "bottom" |
||||
|
} |
||||
|
}, |
||||
|
"left": { |
||||
|
"position": "left", |
||||
|
"attrs": { |
||||
|
"circle": { |
||||
|
"r": 3, |
||||
|
"magnet": true, |
||||
|
"stroke": "#666", |
||||
|
"strokeWidth": 1, |
||||
|
"fill": "#fff" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
"items": [ |
||||
|
{ |
||||
|
"id": "top", |
||||
|
"group": "top" |
||||
|
}, |
||||
|
{ |
||||
|
"id": "right", |
||||
|
"group": "right", |
||||
|
"attrs": { |
||||
|
"text": { |
||||
|
"text": "" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"id": "bottom", |
||||
|
"group": "bottom", |
||||
|
"attrs": { |
||||
|
"text": { |
||||
|
"text": "否" |
||||
|
} |
||||
|
} |
||||
|
}, |
||||
|
{ |
||||
|
"id": "left", |
||||
|
"group": "left" |
||||
|
} |
||||
|
] |
||||
|
}, |
||||
|
"id": "a6da6684-96c8-4595-bec6-a94122b61e30", |
||||
|
"zIndex": 3 |
||||
|
}, |
||||
|
{ |
||||
|
"shape": "edge", |
||||
|
"id": "cbcbd0ea-4a2a-4d2a-8135-7b4b7d7ec50d", |
||||
|
"source": { |
||||
|
"cell": "de868d18-9ec0-4dac-abbe-5cb9e3c20e2f", |
||||
|
"port": "right" |
||||
|
}, |
||||
|
"target": { |
||||
|
"cell": "a6da6684-96c8-4595-bec6-a94122b61e30", |
||||
|
"port": "left" |
||||
|
}, |
||||
|
"zIndex": 4 |
||||
|
}, |
||||
|
{ |
||||
|
"shape": "edge", |
||||
|
"id": "ea10b718-1c66-4a3d-b29c-b0b20e6bba11", |
||||
|
"source": { |
||||
|
"cell": "a6da6684-96c8-4595-bec6-a94122b61e30", |
||||
|
"port": "right" |
||||
|
}, |
||||
|
"target": { |
||||
|
"cell": "81e87610-c749-4667-8382-00c62be30e2b", |
||||
|
"port": "left" |
||||
|
}, |
||||
|
"zIndex": 5, |
||||
|
"labels": [ |
||||
|
{ |
||||
|
"attrs": { |
||||
|
"label": { |
||||
|
"text": "是" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
] |
||||
|
} |
||||
|
] |
||||
|
} |
@ -1,6 +1,7 @@ |
|||||
{ |
{ |
||||
"packages": [ |
"packages": [ |
||||
"packages/*" |
"packages/*", |
||||
|
"example" |
||||
], |
], |
||||
"version": "0.3.3" |
"version": "0.3.3" |
||||
} |
} |
@ -1,43 +0,0 @@ |
|||||
const path = require('path'); |
|
||||
const fs = require('fs-extra'); |
|
||||
|
|
||||
const writeEntryFile = async (basePath, fileIds) => { |
|
||||
const imports = []; |
|
||||
const funcMaps = []; |
|
||||
fileIds.forEach((id, idx) => { |
|
||||
const funcName = `fn_${idx}`; |
|
||||
imports.push(`import ${funcName} from './${id}';`); |
|
||||
funcMaps.push(`'${id}': ${funcName}`); |
|
||||
}); |
|
||||
const fileContent = [ |
|
||||
imports.join('\n'), |
|
||||
`const nodeFns = {\n ${funcMaps.join(',\n ')}\n};`, |
|
||||
'export default nodeFns;', |
|
||||
].join('\n'); |
|
||||
const entryFilePath = path.join(basePath, 'index.js'); |
|
||||
await fs.outputFile(entryFilePath, fileContent, { encoding: 'utf8', flag: 'w' }); |
|
||||
}; |
|
||||
|
|
||||
const writeNodeCodes = async (basePath, dsl) => { |
|
||||
const fileIds = []; |
|
||||
const { cells = [] } = dsl; |
|
||||
const nodes = cells.filter((cell) => cell.shape !== 'edge'); |
|
||||
for (const { |
|
||||
id, |
|
||||
data: { code }, |
|
||||
} of nodes) { |
|
||||
fileIds.push(id); |
|
||||
const filePath = path.join(basePath, id + '.js'); |
|
||||
await fs.outputFile(filePath, code, { encoding: 'utf8', flag: 'w' }); |
|
||||
} |
|
||||
return fileIds; |
|
||||
}; |
|
||||
|
|
||||
const setup = async (dsl, logicBasePath) => { |
|
||||
const nodeCodesPath = path.join(logicBasePath, 'nodeFns'); |
|
||||
await fs.remove(nodeCodesPath); |
|
||||
const fileIds = await writeNodeCodes(nodeCodesPath, dsl); |
|
||||
await writeEntryFile(nodeCodesPath, fileIds); |
|
||||
}; |
|
||||
|
|
||||
module.exports = setup; |
|
@ -1,29 +0,0 @@ |
|||||
const path = require('path'); |
|
||||
const fs = require('fs-extra'); |
|
||||
|
|
||||
const extractObj = (obj = {}, keys = []) => { |
|
||||
const ret = {}; |
|
||||
keys.forEach((key) => (ret[key] = obj[key])); |
|
||||
return ret; |
|
||||
}; |
|
||||
|
|
||||
const simplify = (dsl) => { |
|
||||
const { cells = [] } = dsl; |
|
||||
return { |
|
||||
cells: cells.map((cell) => { |
|
||||
if (cell.shape === 'edge') { |
|
||||
return extractObj(cell, ['id', 'shape', 'source', 'target']); |
|
||||
} else { |
|
||||
return extractObj(cell, ['id', 'shape', 'data']); |
|
||||
} |
|
||||
}), |
|
||||
}; |
|
||||
}; |
|
||||
|
|
||||
const setup = async (dsl, logicBasePath) => { |
|
||||
const simpilifiedDSL = simplify(dsl); |
|
||||
const dslFilePath = path.join(logicBasePath, 'dsl.json'); |
|
||||
await fs.writeFile(dslFilePath, JSON.stringify(simpilifiedDSL, null, 2)); |
|
||||
}; |
|
||||
|
|
||||
module.exports = setup; |
|
@ -0,0 +1,3 @@ |
|||||
|
{ |
||||
|
"extends": "../../.babelrc" |
||||
|
} |
@ -0,0 +1 @@ |
|||||
|
node_modules |
@ -0,0 +1 @@ |
|||||
|
# @imove/compile-code |
@ -0,0 +1,41 @@ |
|||||
|
{ |
||||
|
"name": "@imove/compile-code", |
||||
|
"version": "0.0.1", |
||||
|
"description": "imove compile code", |
||||
|
"main": "dist/core.common.js", |
||||
|
"module": "dist/core.esm.js", |
||||
|
"types": "dist/types/index.d.ts", |
||||
|
"directories": { |
||||
|
"dist": "dist" |
||||
|
}, |
||||
|
"files": [ |
||||
|
"dist" |
||||
|
], |
||||
|
"publishConfig": { |
||||
|
"access": "public", |
||||
|
"registry": "http://registry.npmjs.org/" |
||||
|
}, |
||||
|
"keywords": [ |
||||
|
"imove", |
||||
|
"compile code" |
||||
|
], |
||||
|
"scripts": { |
||||
|
"prepublishOnly": "node scripts/prepublish.js", |
||||
|
"declare-type": "tsc --emitDeclarationOnly", |
||||
|
"build": "rollup -c & npm run declare-type", |
||||
|
"watch": "watch 'npm run build' ./src" |
||||
|
}, |
||||
|
"repository": { |
||||
|
"type": "git", |
||||
|
"url": "git+https://github.com/imgcook/iMove.git" |
||||
|
}, |
||||
|
"author": "smallstonesk", |
||||
|
"license": "MIT", |
||||
|
"dependencies": { |
||||
|
"@antv/x6": "^1.5.1" |
||||
|
}, |
||||
|
"devDependencies": { |
||||
|
"rollup": "^2.7.3", |
||||
|
"watch": "^1.0.2" |
||||
|
} |
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
import path from 'path'; |
||||
|
import rollupBaseConfig from '../../rollup.config'; |
||||
|
import pkg from './package.json'; |
||||
|
|
||||
|
export default Object.assign(rollupBaseConfig, { |
||||
|
input: path.join(__dirname, './src/index.ts'), |
||||
|
output: [ |
||||
|
{ |
||||
|
file: pkg.main, |
||||
|
format: 'cjs', |
||||
|
}, |
||||
|
{ |
||||
|
file: pkg.module, |
||||
|
format: 'es', |
||||
|
}, |
||||
|
] |
||||
|
}); |
@ -0,0 +1,6 @@ |
|||||
|
#!/usr/bin/env node
|
||||
|
|
||||
|
const path = require('path'); |
||||
|
const prePublish = require('../../../scripts/prepublish'); |
||||
|
|
||||
|
prePublish('@imove/compile-code', path.join(__dirname, '../')); |
@ -1,20 +1,16 @@ |
|||||
const path = require('path'); |
|
||||
const fs = require('fs-extra'); |
|
||||
|
|
||||
const INSERT_IMPORT_PLUGINS_COMMENT = '// import plugins here'; |
const INSERT_IMPORT_PLUGINS_COMMENT = '// import plugins here'; |
||||
const INSERT_USE_PLUGINS_COMMENT = '// use plugins here'; |
const INSERT_USE_PLUGINS_COMMENT = '// use plugins here'; |
||||
|
|
||||
const setup = async (plugins = [], logicBasePath) => { |
const addPlugins = (originalCode: string = '', plugins: string[] = []): string => { |
||||
const entryFilePath = path.join(logicBasePath, 'index.js'); |
const modifiedContent: string = originalCode |
||||
const codes = await fs.readFile(entryFilePath, 'utf8'); |
|
||||
const modifiedContent = codes |
|
||||
.replace(new RegExp(INSERT_IMPORT_PLUGINS_COMMENT), () => { |
.replace(new RegExp(INSERT_IMPORT_PLUGINS_COMMENT), () => { |
||||
return plugins.map((plugin, index) => `import plugin${index} from '${plugin}';`).join('\n'); |
return plugins.map((plugin, index) => `import plugin${index} from '${plugin}';`).join('\n'); |
||||
}) |
}) |
||||
.replace(new RegExp(INSERT_USE_PLUGINS_COMMENT), () => { |
.replace(new RegExp(INSERT_USE_PLUGINS_COMMENT), () => { |
||||
return plugins.map((_, index) => `logic.use(plugin${index});`).join('\n'); |
return plugins.map((_, index) => `logic.use(plugin${index});`).join('\n'); |
||||
}); |
}); |
||||
await fs.outputFile(entryFilePath, modifiedContent); |
return modifiedContent; |
||||
}; |
}; |
||||
|
|
||||
module.exports = setup; |
export default addPlugins; |
@ -0,0 +1,52 @@ |
|||||
|
import {Cell} from '@antv/x6'; |
||||
|
|
||||
|
interface DSL { |
||||
|
cells: Cell.Properties[]; |
||||
|
} |
||||
|
|
||||
|
interface INodesFns { |
||||
|
[fileName: string]: string; |
||||
|
} |
||||
|
|
||||
|
const genEntryFile = (nodeIds: string[]): string => { |
||||
|
const imports: string[] = []; |
||||
|
const funcMaps: string[] = []; |
||||
|
nodeIds.forEach((id, idx) => { |
||||
|
const funcName = `fn_${idx}`; |
||||
|
imports.push(`import ${funcName} from './${id}';`); |
||||
|
funcMaps.push(`'${id}': ${funcName}`); |
||||
|
}); |
||||
|
const fileContent: string = [ |
||||
|
imports.join('\n'), |
||||
|
`const nodeFns = {\n ${funcMaps.join(',\n ')}\n};`, |
||||
|
'export default nodeFns;', |
||||
|
].join('\n'); |
||||
|
return fileContent; |
||||
|
}; |
||||
|
|
||||
|
const genNodeFns = (dsl: DSL): INodesFns => { |
||||
|
const nodeFns: INodesFns = {}; |
||||
|
const { cells = [] } = dsl; |
||||
|
const nodes = cells.filter((cell) => cell.shape !== 'edge'); |
||||
|
for (const { |
||||
|
id, |
||||
|
shape, |
||||
|
data: { label, code }, |
||||
|
} of nodes) { |
||||
|
const fileName: string = id + '.js'; |
||||
|
const descData: string = `// ${shape}: ${label}\n`; |
||||
|
const saveData: string = `${descData}\n${code}`; |
||||
|
nodeFns[fileName] = saveData; |
||||
|
} |
||||
|
return nodeFns; |
||||
|
}; |
||||
|
|
||||
|
const extract = (dsl: DSL): INodesFns => { |
||||
|
const nodeFns = genNodeFns(dsl); |
||||
|
const nodeIds = Object.keys(nodeFns).map(fileName => fileName.slice(0, -3)); |
||||
|
const entryFileContent = genEntryFile(nodeIds); |
||||
|
nodeFns['index.js'] = entryFileContent; |
||||
|
return nodeFns; |
||||
|
}; |
||||
|
|
||||
|
export default extract; |
@ -0,0 +1,34 @@ |
|||||
|
import {Cell} from '@antv/x6'; |
||||
|
import addPlugins from './addPlugins'; |
||||
|
import simplifyDSL from './simplifyDSL'; |
||||
|
import extractNodeFns from './extractNodeFns'; |
||||
|
import logicTpl from './template/logic'; |
||||
|
import indexTpl from './template/index'; |
||||
|
import contextTpl from './template/context'; |
||||
|
|
||||
|
interface DSL { |
||||
|
cells: Cell.Properties[]; |
||||
|
} |
||||
|
|
||||
|
interface IOutput { |
||||
|
'nodeFns': { |
||||
|
[fileName: string]: string |
||||
|
}; |
||||
|
'context.js': string; |
||||
|
'dsl.json': string; |
||||
|
'index.js': string; |
||||
|
'logic.js': string; |
||||
|
} |
||||
|
|
||||
|
const compile = (dsl: DSL, plugins = []): IOutput => { |
||||
|
const output: IOutput = { |
||||
|
'nodeFns': extractNodeFns(dsl), |
||||
|
"context.js": contextTpl, |
||||
|
'dsl.json': JSON.stringify(simplifyDSL(dsl), null, 2), |
||||
|
'index.js': addPlugins(indexTpl, plugins), |
||||
|
'logic.js': logicTpl |
||||
|
}; |
||||
|
return output; |
||||
|
}; |
||||
|
|
||||
|
export default compile; |
@ -0,0 +1,32 @@ |
|||||
|
import {Cell} from '@antv/x6'; |
||||
|
|
||||
|
interface DSL { |
||||
|
cells: Cell.Properties[]; |
||||
|
} |
||||
|
|
||||
|
const extractObj = (obj: Cell.Properties = {}, keys: string[] = []): Cell.Properties => { |
||||
|
const ret: Cell.Properties = {}; |
||||
|
keys.forEach((key) => { |
||||
|
if (obj[key]) { |
||||
|
ret[key] = obj[key]; |
||||
|
} |
||||
|
}); |
||||
|
return ret; |
||||
|
}; |
||||
|
|
||||
|
const simplifyDSL = (dsl: DSL): Cell.Properties => { |
||||
|
const { cells = [] } = dsl; |
||||
|
return { |
||||
|
cells: cells.map((cell) => { |
||||
|
if (cell.shape === 'edge') { |
||||
|
return extractObj(cell, ['id', 'shape', 'source', 'target']); |
||||
|
} else { |
||||
|
const newCell = extractObj(cell, ['id', 'shape', 'data']); |
||||
|
newCell.data = extractObj(cell.data, ['trigger', 'configData', 'ports']); |
||||
|
return newCell; |
||||
|
} |
||||
|
}), |
||||
|
}; |
||||
|
}; |
||||
|
|
||||
|
export default simplifyDSL; |
@ -0,0 +1,7 @@ |
|||||
|
{ |
||||
|
"extends": "../../tsconfig", |
||||
|
"compilerOptions": { |
||||
|
"outDir": "dist/types" |
||||
|
}, |
||||
|
"include": ["./src", "./types"] |
||||
|
} |
@ -0,0 +1,86 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
import 'antd/es/modal/style'; |
||||
|
import styles from './index.module.less'; |
||||
|
|
||||
|
import JSZip from 'jszip'; |
||||
|
import { Modal } from 'antd'; |
||||
|
import { Graph } from '@antv/x6'; |
||||
|
import compileCode from '@imove/compile-code'; |
||||
|
import { base64Url2Blob, downloadFile } from '../../../utils'; |
||||
|
|
||||
|
interface IExportModalProps { |
||||
|
flowChart: Graph; |
||||
|
visible: boolean; |
||||
|
onClose: () => void; |
||||
|
} |
||||
|
|
||||
|
const ExportModal: React.FC<IExportModalProps> = (props) => { |
||||
|
|
||||
|
const { flowChart, visible, onClose } = props; |
||||
|
const onExportDSL = () => { |
||||
|
const dsl = JSON.stringify(flowChart.toJSON(), null, 2); |
||||
|
const blob = new Blob([dsl], { type: 'text/plain' }); |
||||
|
downloadFile('imove.dsl.json', blob); |
||||
|
}; |
||||
|
const onExportCode = () => { |
||||
|
const zip = new JSZip(); |
||||
|
const dsl = flowChart.toJSON(); |
||||
|
const output = compileCode(dsl); |
||||
|
Helper.recursiveZip(zip, output); |
||||
|
zip.generateAsync({ type: 'blob' }).then((blob) => { |
||||
|
downloadFile('logic.zip', blob); |
||||
|
}); |
||||
|
}; |
||||
|
const onExportFlowChart = () => { |
||||
|
flowChart.toJPEG((dataUri: string) => { |
||||
|
const blob = base64Url2Blob(dataUri); |
||||
|
downloadFile('flowChart.png', blob); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Modal |
||||
|
width={700} |
||||
|
visible={visible} |
||||
|
footer={null} |
||||
|
title={'导出'} |
||||
|
onOk={onClose} |
||||
|
onCancel={onClose} |
||||
|
> |
||||
|
<div className={styles.modalContent}> |
||||
|
<div className={styles.downloadWrap} onClick={onExportDSL}> |
||||
|
<img className={styles.picPreview} src="//img.alicdn.com/imgextra/i2/O1CN01lvanDL1YKp54hGgMS_!!6000000003041-2-tps-247-247.png" /> |
||||
|
<span>DSL</span> |
||||
|
</div> |
||||
|
<div className={styles.downloadWrap} onClick={onExportCode}> |
||||
|
<img className={styles.picPreview} src="//img.alicdn.com/imgextra/i3/O1CN01V3wg6S27JuqePTXZv_!!6000000007777-2-tps-247-247.png" /> |
||||
|
<span>代码</span> |
||||
|
</div> |
||||
|
<div className={styles.downloadWrap} onClick={onExportFlowChart}> |
||||
|
<img className={styles.picPreview} src="//img.alicdn.com/imgextra/i1/O1CN01sVItyC1exeLraUhf6_!!6000000003938-2-tps-247-247.png" /> |
||||
|
<span>流程图</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Modal> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const Helper = { |
||||
|
recursiveZip(root: JSZip, json: any) { |
||||
|
if (typeof json !== 'object' || json === null) { |
||||
|
return; |
||||
|
} |
||||
|
for (const key in json) { |
||||
|
const val = json[key]; |
||||
|
if (typeof val === 'string') { |
||||
|
root.file(key, val); |
||||
|
} else { |
||||
|
const dir = root.folder(key) as JSZip; |
||||
|
Helper.recursiveZip(dir, val); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default ExportModal; |
@ -0,0 +1,32 @@ |
|||||
|
.container { |
||||
|
margin-left: 20px; |
||||
|
} |
||||
|
|
||||
|
.modalContent { |
||||
|
|
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-around; |
||||
|
width: 100%; |
||||
|
padding: 30px 0; |
||||
|
|
||||
|
.downloadWrap { |
||||
|
|
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
padding: 15px 20px 5px; |
||||
|
border: 0.5px solid #EFEFEF; |
||||
|
|
||||
|
&:hover { |
||||
|
cursor: pointer; |
||||
|
background-color: #EFEFEF; |
||||
|
} |
||||
|
|
||||
|
.picPreview { |
||||
|
width: 150px; |
||||
|
height: 150px; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -0,0 +1,33 @@ |
|||||
|
import React, {useState} from 'react'; |
||||
|
|
||||
|
import styles from './index.module.less'; |
||||
|
|
||||
|
import { Button } from 'antd'; |
||||
|
import { Graph } from '@antv/x6'; |
||||
|
import ExportModal from './exportModal'; |
||||
|
|
||||
|
interface IProps { |
||||
|
flowChart: Graph; |
||||
|
} |
||||
|
|
||||
|
const Export: React.FC<IProps> = props => { |
||||
|
|
||||
|
const {flowChart} = props; |
||||
|
const [modalVisible, setModalVisible] = useState<boolean>(false); |
||||
|
|
||||
|
const onOpenModal = () => setModalVisible(true); |
||||
|
const onCloseModal = () => setModalVisible(false); |
||||
|
|
||||
|
return ( |
||||
|
<div className={styles.container}> |
||||
|
<Button type={'primary'} size={'small'} onClick={onOpenModal}>导出</Button> |
||||
|
<ExportModal |
||||
|
flowChart={flowChart} |
||||
|
visible={modalVisible} |
||||
|
onClose={onCloseModal} |
||||
|
/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Export; |
@ -0,0 +1,3 @@ |
|||||
|
.container { |
||||
|
margin-left: 20px; |
||||
|
} |
@ -0,0 +1,49 @@ |
|||||
|
import React, { useState } from 'react'; |
||||
|
|
||||
|
import styles from './index.module.less'; |
||||
|
|
||||
|
import { Graph } from '@antv/x6'; |
||||
|
import { Button, Upload, message } from 'antd'; |
||||
|
|
||||
|
interface IProps { |
||||
|
flowChart: Graph; |
||||
|
} |
||||
|
|
||||
|
const ImportDSL: React.FC<IProps> = props => { |
||||
|
|
||||
|
const {flowChart} = props; |
||||
|
const [disabled, setDisabled] = useState(false); |
||||
|
const beforeUpload = (file: any) => { |
||||
|
setDisabled(true); |
||||
|
const reader = new FileReader(); |
||||
|
reader.onload = (evt) => { |
||||
|
setDisabled(false); |
||||
|
if(!evt.target) { |
||||
|
message.error('加载文件失败!'); |
||||
|
} else { |
||||
|
const dsl = evt.target.result as string; |
||||
|
try { |
||||
|
flowChart.fromJSON(JSON.parse(dsl)); |
||||
|
} catch (err) { |
||||
|
message.error('DSL解析失败!'); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
reader.readAsText(file); |
||||
|
return false; |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<Upload |
||||
|
className={styles.container} |
||||
|
accept={'.json'} |
||||
|
disabled={disabled} |
||||
|
showUploadList={false} |
||||
|
beforeUpload={beforeUpload} |
||||
|
> |
||||
|
<Button type={'primary'} size={'small'}>导入DSL</Button> |
||||
|
</Upload> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ImportDSL; |
Loading…
Reference in new issue