Browse Source

Merge branch '0.3.x' of github.com:suanmei/iMove into 0.3.x

master
i5ting 4 years ago
parent
commit
46bac17c1a
  1. 5
      README.en-US.md
  2. 9
      README.md
  3. 78
      README.zh-CN.md
  4. 1
      cypress.json
  5. 28
      cypress/integration/examples/actions.spec.js
  6. 45
      cypress/integration/examples/configuration.test.js
  7. 28
      cypress/integration/examples/dataSave.spec.js
  8. 42
      cypress/integration/examples/element.test.js
  9. 100
      cypress/integration/examples/tools.test.js
  10. 15
      example/README.md
  11. 41
      example/mock/api.ts
  12. 336
      example/mock/db.json
  13. 14
      example/package.json
  14. 3
      lerna.json
  15. 13
      package.json
  16. 1
      packages/cli/package.json
  17. 43
      packages/cli/src/cmd/dev/extractCodes.js
  18. 28
      packages/cli/src/cmd/dev/index.js
  19. 29
      packages/cli/src/cmd/dev/simplifyDSL.js
  20. 2
      packages/cli/src/utils/server/index.js
  21. 3
      packages/compile-code/.babelrc
  22. 1
      packages/compile-code/.npmignore
  23. 1
      packages/compile-code/README.md
  24. 41
      packages/compile-code/package.json
  25. 17
      packages/compile-code/rollup.config.js
  26. 6
      packages/compile-code/scripts/prepublish.js
  27. 12
      packages/compile-code/src/addPlugins.ts
  28. 52
      packages/compile-code/src/extractNodeFns.ts
  29. 34
      packages/compile-code/src/index.ts
  30. 32
      packages/compile-code/src/simplifyDSL.ts
  31. 3
      packages/compile-code/src/template/context.ts
  32. 3
      packages/compile-code/src/template/index.ts
  33. 14
      packages/compile-code/src/template/logic.ts
  34. 7
      packages/compile-code/tsconfig.json
  35. 2
      packages/core/package.json
  36. 24
      packages/core/src/mods/flowChart/registerServerStorage.ts
  37. 2
      packages/core/src/mods/header/connectStatus/index.module.less
  38. 86
      packages/core/src/mods/header/export/exportModal.tsx
  39. 32
      packages/core/src/mods/header/export/index.module.less
  40. 33
      packages/core/src/mods/header/export/index.tsx
  41. 3
      packages/core/src/mods/header/importDSL/index.module.less
  42. 49
      packages/core/src/mods/header/importDSL/index.tsx
  43. 7
      packages/core/src/mods/header/index.module.less
  44. 6
      packages/core/src/mods/header/index.tsx
  45. 20
      packages/core/src/utils/index.ts

5
README.en-US.md

@ -29,10 +29,9 @@ Download this project, install dependencies and start the project.
```bash ```bash
$ git clone https://github.com/imgcook/imove.git $ git clone https://github.com/imgcook/imove.git
$ cd ./imove/example $ npm run example
$ npm install
$ npm start
``` ```
Open http://localhost:8000/ and you can see the online effect.
### 步骤 2. 绘制流程图 ### 步骤 2. 绘制流程图

9
README.md

@ -28,11 +28,12 @@
下载仓库,安装并启动 下载仓库,安装并启动
```bash ```bash
$ git clone https://github.com/imgcook/imove.git $ git clone git@github.com:imgcook/imove.git
$ cd ./imove/example $ cd imove
$ npm install $ npm install
$ npm start $ npm run example
``` ```
此时浏览器会自动打开 `http://localhost:8000/` ,可以看到运行效果。
### 步骤 2. 绘制流程图 ### 步骤 2. 绘制流程图
@ -66,6 +67,8 @@ $ npm start
## 贡献者 ✨ ## 贡献者 ✨
感谢 [蚂蚁 X6 团队](https://github.com/antvis/X6) 提供的绘图引擎
感谢所有贡献的人 ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 感谢所有贡献的人 ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section --> <!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->

78
README.zh-CN.md

@ -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) 规范,欢迎贡献!

1
cypress.json

@ -0,0 +1 @@
{}

28
cypress/integration/examples/actions.spec.js

@ -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', () => {
})
})

45
cypress/integration/examples/configuration.test.js

@ -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"`)
})
})

28
cypress/integration/examples/dataSave.spec.js

@ -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', () => {
})
})

42
cypress/integration/examples/element.test.js

@ -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}')
})
})

100
cypress/integration/examples/tools.test.js

@ -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))
})
})

15
example/README.md

@ -1,15 +0,0 @@
# umi project
## Getting Started
Install dependencies,
```bash
$ yarn
```
Start the dev server,
```bash
$ yarn start
```

41
example/mock/api.ts

@ -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,
};

336
example/mock/db.json

@ -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": "是"
}
}
}
]
}
]
}

14
example/package.json

@ -1,5 +1,6 @@
{ {
"private": true, "private": true,
"name": "example",
"scripts": { "scripts": {
"start": "umi dev", "start": "umi dev",
"build": "umi build", "build": "umi build",
@ -19,16 +20,5 @@
"prettier --parser=typescript --write" "prettier --parser=typescript --write"
] ]
}, },
"dependencies": { "dependencies": {}
"@imove/core": "^0.3.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.2",
"antd": "^4.4.3",
"lint-staged": "^10.0.7",
"prettier": "^1.19.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"umi": "^3.2.2",
"yorkie": "^2.0.0"
}
} }

3
lerna.json

@ -1,6 +1,7 @@
{ {
"packages": [ "packages": [
"packages/*" "packages/*",
"example"
], ],
"version": "0.3.3" "version": "0.3.3"
} }

13
package.json

@ -2,6 +2,7 @@
"name": "root", "name": "root",
"private": true, "private": true,
"scripts": { "scripts": {
"example": "npx lerna run --scope example start",
"lint": "eslint --cache --fix --ext .ts,tsx,js,jsx ./packages/**/src > ./.eslint-error.log; exit 0", "lint": "eslint --cache --fix --ext .ts,tsx,js,jsx ./packages/**/src > ./.eslint-error.log; exit 0",
"test": "jest --coverage", "test": "jest --coverage",
"e2e": "cypress open", "e2e": "cypress open",
@ -50,6 +51,7 @@
"jest-environment-jsdom-fourteen": "^1.0.1", "jest-environment-jsdom-fourteen": "^1.0.1",
"jest-puppeteer": "^4.4.0", "jest-puppeteer": "^4.4.0",
"lerna": "^3.20.2", "lerna": "^3.20.2",
"lowdb": "^1.0.0",
"prettier": "^2.0.4", "prettier": "^2.0.4",
"puppeteer": "^5.5.0", "puppeteer": "^5.5.0",
"react-app-polyfill": "^2.0.0", "react-app-polyfill": "^2.0.0",
@ -66,6 +68,15 @@
"@babel/runtime-corejs3": "^7.9.2", "@babel/runtime-corejs3": "^7.9.2",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"dumi": "^1.0.37", "dumi": "^1.0.37",
"ora": "^4.0.3" "ora": "^4.0.3",
"@imove/core": "^0.3.0",
"@umijs/preset-react": "1.x",
"@umijs/test": "^3.2.2",
"antd": "^4.4.3",
"lint-staged": "^10.0.7",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"umi": "^3.2.2",
"yorkie": "^2.0.0"
} }
} }

1
packages/cli/package.json

@ -20,6 +20,7 @@
"author": "smallstonesk", "author": "smallstonesk",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@imove/compile-code": "^0.0.1",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"commander": "^6.0.0", "commander": "^6.0.0",
"cors": "^2.8.5", "cors": "^2.8.5",

43
packages/cli/src/cmd/dev/extractCodes.js

@ -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;

28
packages/cli/src/cmd/dev/index.js

@ -2,17 +2,27 @@ const path = require('path');
const fs = require('fs-extra'); const fs = require('fs-extra');
const Base = require('../base'); const Base = require('../base');
const mergePkg = require('./mergePkg'); const mergePkg = require('./mergePkg');
const addPlugins = require('./addPlugins'); const compileCode = require('@imove/compile-code');
const simplifyDSL = require('./simplifyDSL');
const extractCodes = require('./extractCodes');
const { createServer } = require('../../utils/server'); const { createServer } = require('../../utils/server');
const noop = () => {}; const noop = () => {};
const TPL_PATH = path.join(__dirname, './template');
const CACHE_PATH = path.join(process.cwd(), './.cache'); const CACHE_PATH = path.join(process.cwd(), './.cache');
const CACHE_DSL_FILE = path.join(CACHE_PATH, 'imove.dsl.json'); const CACHE_DSL_FILE = path.join(CACHE_PATH, 'imove.dsl.json');
class Dev extends Base { class Dev extends Base {
async writeOutputIntoFiles(curPath, output) {
for(let key in output) {
const newPath = path.join(curPath, key);
if(path.extname(newPath)) {
await fs.writeFile(newPath, output[key]);
} else {
await fs.ensureDir(newPath);
await this.writeOutputIntoFiles(newPath, output[key]);
}
}
}
async save(req, res) { async save(req, res) {
const { outputPath, plugins = [] } = this.config; const { outputPath, plugins = [] } = this.config;
@ -28,10 +38,8 @@ class Dev extends Base {
// compile // compile
try { try {
const { dsl } = req.body; const { dsl } = req.body;
await simplifyDSL(dsl, outputPath); const output = compileCode(dsl, plugins);
await extractCodes(dsl, outputPath); await this.writeOutputIntoFiles(outputPath, output);
await fs.copy(TPL_PATH, outputPath);
await addPlugins(plugins, outputPath);
await mergePkg(dsl, this.projectPath); await mergePkg(dsl, this.projectPath);
await fs.outputFile(CACHE_DSL_FILE, JSON.stringify(dsl, null, 2)); await fs.outputFile(CACHE_DSL_FILE, JSON.stringify(dsl, null, 2));
res.status(200).json({ isCompiled: true }).end(); res.status(200).json({ isCompiled: true }).end();
@ -50,8 +58,8 @@ class Dev extends Base {
run() { run() {
const app = createServer(); const app = createServer();
app.post('/api/save', this.save); app.post('/api/save', this.save.bind(this));
app.get('/api/connect', this.connect); app.get('/api/connect', this.connect.bind(this));
} }
} }

29
packages/cli/src/cmd/dev/simplifyDSL.js

@ -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;

2
packages/cli/src/utils/server/index.js

@ -3,7 +3,7 @@ const bodyParser = require('body-parser');
const createServer = (port = 3500) => { const createServer = (port = 3500) => {
const app = express(); const app = express();
app.use(bodyParser.json()); app.use(bodyParser.json({ limit: '50mb' }));
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
app.use((req, res, next) => { app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Origin', '*');

3
packages/compile-code/.babelrc

@ -0,0 +1,3 @@
{
"extends": "../../.babelrc"
}

1
packages/compile-code/.npmignore

@ -0,0 +1 @@
node_modules

1
packages/compile-code/README.md

@ -0,0 +1 @@
# @imove/compile-code

41
packages/compile-code/package.json

@ -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"
}
}

17
packages/compile-code/rollup.config.js

@ -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',
},
]
});

6
packages/compile-code/scripts/prepublish.js

@ -0,0 +1,6 @@
#!/usr/bin/env node
const path = require('path');
const prePublish = require('../../../scripts/prepublish');
prePublish('@imove/compile-code', path.join(__dirname, '../'));

12
packages/cli/src/cmd/dev/addPlugins.js → packages/compile-code/src/addPlugins.ts

@ -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;

52
packages/compile-code/src/extractNodeFns.ts

@ -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;

34
packages/compile-code/src/index.ts

@ -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;

32
packages/compile-code/src/simplifyDSL.ts

@ -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;

3
packages/cli/src/cmd/dev/template/context.js → packages/compile-code/src/template/context.ts

@ -1,4 +1,4 @@
export default class Context { export default `export default class Context {
constructor(opts) { constructor(opts) {
this._init(opts); this._init(opts);
} }
@ -42,3 +42,4 @@ export default class Context {
}); });
} }
} }
`;

3
packages/cli/src/cmd/dev/template/index.js → packages/compile-code/src/template/index.ts

@ -1,4 +1,4 @@
import Logic from './logic'; export default `import Logic from './logic';
import dsl from './dsl.json'; import dsl from './dsl.json';
// import plugins here // import plugins here
@ -7,3 +7,4 @@ const logic = new Logic({ dsl });
// use plugins here // use plugins here
export default logic; export default logic;
`;

14
packages/cli/src/cmd/dev/template/logic.js → packages/compile-code/src/template/logic.ts

@ -1,4 +1,4 @@
import nodeFns from './nodeFns'; export default `import nodeFns from './nodeFns';
import Context from './context'; import Context from './context';
import EventEmitter from 'eventemitter3'; import EventEmitter from 'eventemitter3';
@ -34,7 +34,7 @@ export default class Logic extends EventEmitter {
_runLifecycleEvent(eventName, ctx) { _runLifecycleEvent(eventName, ctx) {
if (!LIFECYCLE.has(eventName)) { if (!LIFECYCLE.has(eventName)) {
return console.warn(`Lifecycle ${eventName} is not supported!`); return console.warn(\`Lifecycle \${eventName} is not supported!\`);
} }
if (this.lifeCycleEvents[eventName]) { if (this.lifeCycleEvents[eventName]) {
this.lifeCycleEvents[eventName].forEach((fn) => fn(ctx)); this.lifeCycleEvents[eventName].forEach((fn) => fn(ctx));
@ -66,10 +66,7 @@ export default class Logic extends EventEmitter {
const { ports } = curNode.data; const { ports } = curNode.data;
for (const key in ports) { for (const key in ports) {
const { condition } = ports[key]; const { condition } = ports[key];
const ret = ((ctx) => { const ret = new Function('ctx', 'return ' + condition)(ctx);
return condition;
})(ctx);
// const ret = new Function('ctx', 'return ' + condition)(ctx);
if (ret === Boolean(curRet)) { if (ret === Boolean(curRet)) {
matchedPort = key; matchedPort = key;
break; break;
@ -101,7 +98,7 @@ export default class Logic extends EventEmitter {
continue; continue;
} }
if (!LIFECYCLE.has(eventName)) { if (!LIFECYCLE.has(eventName)) {
console.warn(`Lifecycle ${eventName} is not supported in imove.`); console.warn(\`Lifecycle \${eventName} is not supported in imove.\`);
continue; continue;
} }
if (!this.lifeCycleEvents[eventName]) { if (!this.lifeCycleEvents[eventName]) {
@ -129,9 +126,10 @@ export default class Logic extends EventEmitter {
async invoke(trigger, data) { async invoke(trigger, data) {
const curNode = this._getStartNode(trigger); const curNode = this._getStartNode(trigger);
if (!curNode) { if (!curNode) {
return Promise.reject(new Error(`Invoke failed! No logic-start named ${trigger} found!`)); return Promise.reject(new Error(\`Invoke failed! No logic-start named \${trigger} found!\`));
} }
const ctx = this._createCtx({ payload: data }); const ctx = this._createCtx({ payload: data });
await this._execNode(ctx, curNode); await this._execNode(ctx, curNode);
} }
} }
`;

7
packages/compile-code/tsconfig.json

@ -0,0 +1,7 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"outDir": "dist/types"
},
"include": ["./src", "./types"]
}

2
packages/core/package.json

@ -38,9 +38,11 @@
"dependencies": { "dependencies": {
"@ant-design/icons": "^4.0.6", "@ant-design/icons": "^4.0.6",
"@antv/x6": "^1.3.0", "@antv/x6": "^1.3.0",
"@imove/compile-code": "^0.0.1",
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"antd": "^4.5.3", "antd": "^4.5.3",
"axios": "^0.21.0", "axios": "^0.21.0",
"jszip": "^3.5.0",
"lodash.merge": "^4.6.2", "lodash.merge": "^4.6.2",
"query-string": "^6.13.7", "query-string": "^6.13.7",
"react-ace": "^9.1.3", "react-ace": "^9.1.3",

24
packages/core/src/mods/flowChart/registerServerStorage.ts

@ -17,26 +17,28 @@ const validate = (type: string, data: any) => {
} }
}; };
const enqueue = (type: string, actionType: ActionType, data: any) => { const enqueue = (cellType: string, actionType: ActionType, data: any) => {
if (!validate(type, data)) { if (!validate(cellType, data)) {
return; return;
} }
const foundIndex = memQueue.findIndex( const foundIndex = memQueue.findIndex((item) => (
(item) => item.type === type && item.actionType === actionType item.type === cellType &&
); item.actionType === actionType &&
item.data.id === data.id
));
if (foundIndex > -1) { if (foundIndex > -1) {
const deleted = memQueue.splice(foundIndex, 1)[0]; const deleted = memQueue.splice(foundIndex, 1)[0];
data = merge(deleted, data); merge(deleted.data, data);
} }
memQueue.push({ type, actionType, data }); memQueue.push({ type: cellType, actionType, data });
}; };
let modifyActionTimer = -1; let modifyActionTimer: number = -1;
const save = (flowChart: Graph, type: string, actionType: ActionType, data: any) => { const save = (flowChart: Graph, cellType: string, actionType: ActionType, data: any) => {
enqueue(type, actionType, data); enqueue(cellType, actionType, data);
clearTimeout(modifyActionTimer); clearTimeout(modifyActionTimer);
modifyActionTimer = setTimeout(() => { modifyActionTimer = window.setTimeout(() => {
const pushedActions = memQueue.slice(0); const pushedActions = memQueue.slice(0);
if (pushedActions.length > 0) { if (pushedActions.length > 0) {
flowChart.trigger('graph:change:modify'); flowChart.trigger('graph:change:modify');

2
packages/core/src/mods/header/connectStatus/index.module.less

@ -3,7 +3,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
margin-right: 20px; margin-left: 20px;
.tag { .tag {
cursor: pointer; cursor: pointer;

86
packages/core/src/mods/header/export/exportModal.tsx

@ -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;

32
packages/core/src/mods/header/export/index.module.less

@ -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;
}
}
}

33
packages/core/src/mods/header/export/index.tsx

@ -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;

3
packages/core/src/mods/header/importDSL/index.module.less

@ -0,0 +1,3 @@
.container {
margin-left: 20px;
}

49
packages/core/src/mods/header/importDSL/index.tsx

@ -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;

7
packages/core/src/mods/header/index.module.less

@ -6,11 +6,16 @@
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 0 20px;
background-color: #FEFEFE; background-color: #FEFEFE;
} }
.titleText { .titleText {
margin-left: 20px;
font-size: 24px; font-size: 24px;
font-weight: 500; font-weight: 500;
} }
.widgets {
display: flex;
flex-direction: row;
}

6
packages/core/src/mods/header/index.tsx

@ -3,6 +3,8 @@ import React from 'react';
import styles from './index.module.less'; import styles from './index.module.less';
import { Graph } from '@antv/x6'; import { Graph } from '@antv/x6';
import Export from './export';
import ImportDSL from './importDSL';
import ConnectStatus from './connectStatus'; import ConnectStatus from './connectStatus';
interface IProps { interface IProps {
@ -14,8 +16,12 @@ const Header: React.FC<IProps> = (props) => {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<span className={styles.titleText}>iMove</span> <span className={styles.titleText}>iMove</span>
<div className={styles.widgets}>
<Export flowChart={flowChart} />
<ImportDSL flowChart={flowChart} />
<ConnectStatus flowChart={flowChart} /> <ConnectStatus flowChart={flowChart} />
</div> </div>
</div>
); );
}; };

20
packages/core/src/utils/index.ts

@ -36,3 +36,23 @@ const parseConfig = {
export const parseQuery = (): { [key: string]: any } => { export const parseQuery = (): { [key: string]: any } => {
return parse(location.search, parseConfig); return parse(location.search, parseConfig);
}; };
export const base64Url2Blob = (base64: string) => {
const bytes = window.atob(base64.split(',')[1]);
const buffer = new ArrayBuffer(bytes.length);
const arr = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; i++) {
arr[i] = bytes.charCodeAt(i);
}
return new Blob([arr], { type: 'image/png' });
};
export const downloadFile = (fileName: string, blob: Blob) => {
const elem = document.createElement('a');
elem.href = URL.createObjectURL(blob);
elem.download = fileName;
document.body.appendChild(elem);
elem.click();
URL.revokeObjectURL(elem.href);
document.body.removeChild(elem);
};

Loading…
Cancel
Save