├── .all-contributorsrc
├── .circleci
└── config.yml
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── checker.yml
├── .gitignore
├── .prettierignore
├── .prettierrc
├── CONTRIBUTING.md
├── LICENSE
├── README.en-US.md
├── README.md
├── __tests__
└── core
│ └── src
│ ├── api
│ └── index.test.js
│ ├── common
│ ├── baseCell.test.js
│ ├── const.test.js
│ ├── previewCell.test.js
│ └── shortcuts.test.js
│ ├── components
│ ├── colorPicker.test.js
│ ├── miniMapSimpleNode.test.js
│ └── xIcon.test.js
│ ├── mods
│ ├── flowChart.test.js
│ ├── header.test.jsx
│ ├── layout.test.js
│ ├── settingBar.test.js
│ ├── sideBar.test.js
│ └── toolBar.test.js
│ └── utils
│ ├── flowChartUtils.test.js
│ └── index.test.js
├── config
├── cli.jest.config.js
├── core.jest.config.js
├── editor.jest.config.js
└── plugin.jest.config.js
├── cypress.json
├── cypress
├── fixtures
│ └── example.json
├── integration
│ └── examples
│ │ ├── configuration.test.js
│ │ ├── element.test.js
│ │ └── tools.test.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── docs
├── api
│ ├── CONTRIBUTING.md
│ ├── ctx.md
│ ├── data.md
│ ├── edge.md
│ ├── group.md
│ ├── layout.md
│ ├── module.md
│ ├── node.md
│ └── schema.md
├── config
│ └── index.md
├── docs
│ └── index.md
├── guide
│ └── index.md
└── plugins
│ └── index.md
├── example
├── .env
├── .umirc.ts
├── mock
│ ├── .gitkeep
│ ├── api.ts
│ └── db.json
├── package.json
├── src
│ └── pages
│ │ ├── index.less
│ │ └── index.tsx
├── tsconfig.json
└── typings.d.ts
├── lerna.json
├── mlc_config.json
├── package.json
├── packages
├── cli
│ ├── .npmignore
│ ├── README.md
│ ├── index.js
│ ├── package.json
│ └── src
│ │ ├── cmd
│ │ ├── base
│ │ │ └── index.js
│ │ ├── dev
│ │ │ ├── index.js
│ │ │ └── mergePkg.js
│ │ ├── editor
│ │ │ ├── build.js
│ │ │ ├── index.js
│ │ │ └── template
│ │ │ │ ├── app.jsx
│ │ │ │ └── index.html
│ │ ├── index.js
│ │ └── init
│ │ │ ├── index.js
│ │ │ └── template
│ │ │ └── imove.config.js.tpl
│ │ └── utils
│ │ ├── getConfig.js
│ │ └── server
│ │ └── index.js
├── compile-code
│ ├── .npmignore
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.js
│ ├── scripts
│ │ └── prepublish.js
│ ├── src
│ │ ├── addPlugins.ts
│ │ ├── compileForOnline.ts
│ │ ├── compileForProject.ts
│ │ ├── extractNodeFns.ts
│ │ ├── index.ts
│ │ ├── simplifyDSL.ts
│ │ └── template
│ │ │ ├── context.ts
│ │ │ ├── index.ts
│ │ │ ├── logic.ts
│ │ │ └── runOnline.ts
│ └── tsconfig.json
├── core
│ ├── .npmignore
│ ├── .npmrc
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.js
│ ├── scripts
│ │ └── prepublish.js
│ ├── src
│ │ ├── api
│ │ │ └── index.ts
│ │ ├── common
│ │ │ ├── baseCell
│ │ │ │ ├── behavior.ts
│ │ │ │ ├── branch.ts
│ │ │ │ ├── index.ts
│ │ │ │ └── start.ts
│ │ │ ├── const.ts
│ │ │ ├── previewCell
│ │ │ │ ├── behavior.tsx
│ │ │ │ ├── branch.tsx
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.ts
│ │ │ │ └── start.tsx
│ │ │ └── shortcuts.ts
│ │ ├── components
│ │ │ ├── codeEditor
│ │ │ │ ├── index.tsx
│ │ │ │ └── theme-monokai.ts
│ │ │ ├── codeRun
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── inputPanel
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ ├── colorPicker
│ │ │ │ └── index.tsx
│ │ │ ├── console
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── miniMapSimpleNode
│ │ │ │ └── index.tsx
│ │ │ ├── schemaForm
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ └── xIcon
│ │ │ │ ├── index.less
│ │ │ │ └── index.tsx
│ │ ├── global.index.less
│ │ ├── hooks
│ │ │ └── useClickAway.ts
│ │ ├── index.tsx
│ │ ├── mods
│ │ │ ├── flowChart
│ │ │ │ ├── codeEditorModal
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── codeRunModal
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── contextMenu
│ │ │ │ │ ├── index.tsx
│ │ │ │ │ └── menuConfig
│ │ │ │ │ │ ├── blank.tsx
│ │ │ │ │ │ ├── index.ts
│ │ │ │ │ │ └── node.tsx
│ │ │ │ ├── createFlowChart.ts
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── registerServerStorage.ts
│ │ │ ├── header
│ │ │ │ ├── configuration
│ │ │ │ │ ├── editModal.tsx
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── connectStatus
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── export
│ │ │ │ │ ├── exportModal.tsx
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── guide
│ │ │ │ │ ├── guideModal.tsx
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── importDSL
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── layout
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ ├── settingBar
│ │ │ │ ├── components
│ │ │ │ │ ├── checkbox
│ │ │ │ │ │ ├── index.module.less
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ ├── input
│ │ │ │ │ │ ├── index.module.less
│ │ │ │ │ │ └── index.tsx
│ │ │ │ │ └── json
│ │ │ │ │ │ ├── index.less
│ │ │ │ │ │ ├── index.module.less
│ │ │ │ │ │ ├── index.tsx
│ │ │ │ │ │ └── json.ts
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── mods
│ │ │ │ │ ├── basic
│ │ │ │ │ ├── index.module.less
│ │ │ │ │ └── index.tsx
│ │ │ │ │ └── testCase
│ │ │ │ │ └── index.tsx
│ │ │ ├── sideBar
│ │ │ │ ├── index.module.less
│ │ │ │ └── index.tsx
│ │ │ └── toolBar
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ └── widgets
│ │ │ │ ├── bgColor.tsx
│ │ │ │ ├── bold.tsx
│ │ │ │ ├── borderColor.tsx
│ │ │ │ ├── bringToBack.tsx
│ │ │ │ ├── bringToTop.tsx
│ │ │ │ ├── common
│ │ │ │ ├── makeBtnWidget.tsx
│ │ │ │ └── makeDropdownWidget.tsx
│ │ │ │ ├── fitWindow.tsx
│ │ │ │ ├── fontSize.tsx
│ │ │ │ ├── horizontalAlign.tsx
│ │ │ │ ├── index.module.less
│ │ │ │ ├── index.tsx
│ │ │ │ ├── italic.tsx
│ │ │ │ ├── lineStyle.tsx
│ │ │ │ ├── modifyStatus.tsx
│ │ │ │ ├── nodeAlign.tsx
│ │ │ │ ├── redo.tsx
│ │ │ │ ├── save.tsx
│ │ │ │ ├── textColor.tsx
│ │ │ │ ├── underline.tsx
│ │ │ │ ├── undo.tsx
│ │ │ │ ├── verticalAlign.tsx
│ │ │ │ └── zoom.tsx
│ │ ├── typings.d.ts
│ │ └── utils
│ │ │ ├── analyzeDeps.ts
│ │ │ ├── flowChartUtils.ts
│ │ │ └── index.ts
│ └── tsconfig.json
└── plugin-store
│ ├── .npmignore
│ ├── README.md
│ ├── package.json
│ ├── rollup.config.js
│ ├── scripts
│ └── prepublish.js
│ ├── src
│ └── index.ts
│ └── tsconfig.json
├── rollup.config.js
├── sandbox.config.json
├── scripts
└── prepublish.js
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "iMove",
3 | "projectOwner": "i5ting",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "commitConvention": "angular",
12 | "contributors": [],
13 | "contributorsPerLine": 7
14 | }
15 |
--------------------------------------------------------------------------------
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/node:latest
6 |
7 | working_directory: ~/repo
8 |
9 | steps:
10 | - checkout
11 |
12 | # 恢复缓存
13 | - restore_cache:
14 | key: node-modules-{{ checksum "package.json" }}
15 | # 安装依赖
16 | - run:
17 | name: Install Dependencies
18 | command: yarn --frozen-lockfile
19 | # 格式化代码
20 | - run:
21 | name: Format Code
22 | command: yarn lint
23 | # 写缓存
24 | - save_cache:
25 | key: node-modules-{{ checksum "package-lock.json" }}
26 | paths:
27 | - ./node_modules
28 | - ~/.cache/yarn
29 | # 代码打包构建
30 | # - run:
31 | # name: Run Build
32 | # command: yarn build
33 | # 跑单元测试代码
34 | - run:
35 | name: Run Tests
36 | command: yarn test
37 | no_output_timeout: 300m
38 | # 跑端对端测试代码
39 | - run:
40 | name: Run E2E Tests
41 | command: yarn e2e
42 | no_output_timeout: 300m
43 | # 集成文档
44 | - run:
45 | name: Generate Doc
46 | command: yarn doc
47 | no_output_timeout: 300m
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | .circleci
2 | .github
3 | .husky
4 |
5 | dist/
6 | node_modules/
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | "prettier/@typescript-eslint",
4 | "plugin:react/recommended",
5 | "plugin:@typescript-eslint/recommended",
6 | "plugin:prettier/recommended"
7 | ],
8 | env: {
9 | node: true,
10 | browser: true,
11 | },
12 | settings: {
13 | react: {
14 | version: "detect"
15 | }
16 | },
17 | rules: {
18 | 'prettier/prettier': 'error',
19 | 'react/jsx-filename-extension': ['warn', { extensions: ['.ts', '.tsx'] }], // 修复 tsx 文件引用 tsx 文件报错的问题
20 | "no-var": 0,//禁用var,用let和const代替
21 | "no-use-before-define": 2,//未定义前不能使用
22 | "no-unused-expressions": 2,//禁止无用的表达式
23 | "space-before-function-paren": 'off',
24 | 'comma-dangle': 'off',
25 | 'import/extensions': 'off',
26 | },
27 | overrides: [{
28 | files: ['*.ts', '*.tsx'],
29 | parser: '@typescript-eslint/parser',
30 | plugins: ['@typescript-eslint'],
31 | extends: [
32 | 'plugin:@typescript-eslint/eslint-recommended',
33 | 'plugin:@typescript-eslint/recommended',
34 | 'prettier/@typescript-eslint',
35 | ],
36 | rules: {
37 | '@typescript-eslint/no-explicit-any': 'off',
38 | 'no-param-reassign': 'off',
39 | '@typescript-eslint/interface-name-prefix': 'off',
40 | '@typescript-eslint/no-use-before-define': 'off',
41 | '@typescript-eslint/no-unused-vars': 0,
42 | '@typescript-eslint/explicit-function-return-type': 0,
43 | },
44 | },],
45 | };
46 |
--------------------------------------------------------------------------------
/.github/workflows/checker.yml:
--------------------------------------------------------------------------------
1 | name: 🐱 Checker
2 |
3 | on:
4 | pull_request:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | # 确保 PR 中超链接有效性
10 | markdown-link-checker:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v2
14 | - uses: gaurav-nelson/github-action-markdown-link-check@v1
15 | with:
16 | use-quiet-mode: 'yes'
17 | use-verbose-mode: 'yes'
18 | check-modified-files-only: 'yes'
19 |
20 | # 检查 PR 中英文单词拼写正确性
21 | spell-checker:
22 | runs-on: ubuntu-latest
23 | steps:
24 | - uses: actions/checkout@v2
25 | - name: Install mispell
26 | run: |
27 | wget -O - -q https://git.io/misspell | sh -s -- -b .
28 | - name: Check
29 | run: |
30 | find -E . -regex ".*\.(ts|js|json)" -type f | xargs ./misspell -error
31 | find docs -type f | xargs ./misspell -error
32 | find . -type f -maxdepth 1 | xargs ./misspell -error
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .vscode
3 | .eslintcache
4 | .size-snapshot.json
5 | .husky
6 | npm-debug.log*
7 | yarn-error.log*
8 | lerna-debug.log
9 | node_modules
10 | coverage
11 | lib
12 | es
13 | # production
14 | dist
15 |
16 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
17 |
18 | # dependencies
19 | yarn.lock
20 | package-lock.json
21 |
22 | # misc
23 | .DS_Store
24 |
25 | # umi
26 | example/node_modules
27 | example/src/.umi
28 | example/src/.umi-production
29 | example/src/.umi-test
30 | example/.env.local
31 |
32 | # eslint
33 | .eslint-error.log
34 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .cache
2 | package.json
3 | package-lock.json
4 | public
5 |
6 | **/*.md
7 | **/*.svg
8 | **/*.ejs
9 | **/*.html
10 |
11 | example/.umi
12 | example/.umi-production
13 | example/.umi-test
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "endOfLine": "lf",
3 | "semi": true,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "trailingComma": "all",
7 | "printWidth": 80,
8 | "overrides": [
9 | {
10 | "files": ".prettierrc",
11 | "options": { "parser": "json" }
12 | }
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 拾邑
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.en-US.md:
--------------------------------------------------------------------------------
1 | # iMove
2 |
3 |
4 |
5 | [](#contributors-)
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | iMove is a logic-reusable, function-oriented and process-visualized JavaScript tool library.
15 |
16 |
17 | [English](./README.en-US.md) | 简体中文
18 |
19 | ## Features
20 |
21 | - [x] **Process visualization:** iMove is easy to use and easy to draw. Its logical expression is more intuitive and easy to understand.
22 | - [x] **Logic re-usage:** iMove node supports multiplexing, and its single node supports parameter configuration.
23 | - [x] **Flexible**: We need to write an only function. The node can also be extended. iMove can also support plug-in integration.
24 | - [ ] **Multi-language compilation**: There is no language compiling code limitation (example: support JavaScript, Java compiling code).
25 |
26 | ## Quickstart
27 |
28 | ### Step1. Run
29 |
30 | Download this project, install dependencies and start the project.
31 |
32 | ```bash
33 | $ git clone https://github.com/ykfe/imove.git
34 | $ cd imove/example
35 | $ npm install
36 | $ npm start
37 | ```
38 |
39 | Open http://localhost:8000/ and you can see the online effect.
40 |
41 |
42 | ### Step2. Draw flowchart
43 |
44 | Drag nodes from the left panel and drop them into the center, then we can get a flowchart.
45 |
46 | 
47 |
48 | ### Step3. Configure nodes
49 |
50 | Select the node, modify its display name and complete the code.
51 |
52 | 
53 |
54 | 
55 |
56 | ## Contributing
57 |
58 | 1. Fork this repository
59 | 2. Create a new branch (`git checkout -b my-new-feature`)
60 | 3. Commit your changes (`git commit -am 'Add some feature'`)
61 | 4. Push (`git push origin my-new-feature`)
62 | 5. File a PR
63 |
64 | ## Welcome to fork and feedback
65 |
66 | If you have any suggestion, welcome to GitHub to raise [issues](https://github.com/imgcook/imove/issues).
67 |
68 | ## License
69 |
70 | This project follows the [MIT](http://www.opensource.org/licenses/MIT) license.
71 |
72 | ## Contributors ✨
73 |
74 | Thanks goes to these excellent ([contributors](https://allcontributors.org/docs/en/emoji-key)):
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!
85 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | [![gitter][gitter-image]][gitter-url]
5 | [![NPM version][npm-image]][npm-url]
6 | [![build status][travis-image]][travis-url]
7 | [![Test coverage][coveralls-image]][coveralls-url]
8 | [![PR's Welcome][pr-welcoming-image]][pr-welcoming-url]
9 |
10 |
11 |
12 |
13 | INACTIVE: iMove 是一个逻辑可复用的,面向函数的,流程可视化的 JavaScript 工具库。
14 |
15 |
16 | [English](./README.en-US.md) | 简体中文
17 |
18 | iMove是一个面向前端开发者的逻辑编排工具,核心解决的是复杂逻辑复用的问题。
19 |
20 | iMove由2部分组成:画布和imove-sdk。通过本地起一个http服务运行画布,在画布上完成代码编写和节点编排,最终将流程导出dsl,放到项目中,通过imove-sdk调用执行。
21 |
22 | ## 特性
23 |
24 | - [x] **流程可视化**:上手简单,绘图方便,逻辑表达更直观,易于理解
25 | - [x] **逻辑复用**:iMove 节点支持复用,单节点支持参数配置
26 | - [x] **灵活可扩展**:仅需写一个函数,节点可扩展,支持插件集成
27 | - [x] **适用于JavaScript所有场景**:比如前端点击事件,Ajax 请求和 Node.js 后端 API等
28 | - [ ] **多语言编译**:无语言编译出码限制(例:支持 JavaScript, Java 编译出码)
29 |
30 | ## 使用场景
31 |
32 | 
33 |
34 | 1. 前端流程:比如点击事件,组件生命周期回调等。
35 | 1. 后端流程:比如 Node.js 或 Serverless 领域。
36 | 1. 前端+后端:比如前端点击事件,Ajax 请求和后端 API。
37 |
38 | ## 快速开始
39 |
40 | ### 步骤 1. 准备
41 |
42 | 下载仓库,安装并启动
43 |
44 | ```bash
45 | $ git clone https://github.com/ykfe/imove.git
46 | $ cd imove/example
47 | $ npm install
48 | $ npm start
49 | ```
50 |
51 |
52 | 此时浏览器会自动打开 `http://localhost:8000/` ,可以看到运行效果。
53 |
54 |
55 | ### 步骤 2. 绘制流程图
56 |
57 | 从左侧拖动节点至中央画布,绘制流程图
58 |
59 | 
60 |
61 | ### 步骤 3. 配置节点
62 |
63 | 选择节点,修改节点名,编辑节点代码
64 |
65 | 
66 |
67 | 
68 |
69 | ## Authors
70 |
71 | * qilei0529 - 飞羽「Leader」
72 | * SmallStoneSK - 菉竹「Core Team」
73 | * suanmei - 拾邑「Core Team」
74 | * iloveyou11 - 冷卉「Core Team」
75 | * i5ting - 狼叔「Core Team」
76 |
77 | 团队博客,各种原理,设计初衷,实现,思考等都在这里: https://www.yuque.com/imove/blog
78 |
79 | See also the list of [contributors](https://github.com/imgcook/imove/graphs/contributors) who participated in this project.
80 |
81 | ## 贡献
82 |
83 | 1. Fork 仓库
84 | 2. 创建分支 (`git checkout -b my-new-feature`)
85 | 3. 提交修改 (`git commit -am 'Add some feature'`)
86 | 4. 推送 (`git push origin my-new-feature`)
87 | 5. 创建 PR
88 |
89 | ## 欢迎 fork 和反馈
90 |
91 | 如有建议或意见,欢迎在 github [issues](https://github.com/imgcook/imove/issues) 区提问
92 |
93 | ## 协议
94 |
95 | 本仓库遵循 [MIT 协议](http://www.opensource.org/licenses/MIT)
96 |
97 | ## 贡献者 ✨
98 |
99 | 感谢 [蚂蚁 X6 团队](https://github.com/antvis/X6) 提供的绘图引擎
100 |
101 | 感谢所有贡献的人 ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | 本仓库遵循 [all-contributors](https://github.com/all-contributors/all-contributors) 规范,欢迎贡献!
111 |
112 | [npm-image]: https://img.shields.io/npm/v/@imove/core.svg?style=flat-square
113 | [npm-url]: https://www.npmjs.com/package/@imove/core
114 | [travis-image]: https://img.shields.io/travis/imgcook/imove/master.svg?style=flat-square
115 | [travis-url]: https://travis-ci.org/imgcook/imove/
116 | [coveralls-image]: https://img.shields.io/codecov/c/github/imgcook/imove.svg?style=flat-square
117 | [coveralls-url]: https://codecov.io/github/imgcook/imove?branch=master
118 | [backers-image]: https://opencollective.com/imgcook/imove/backers/badge.svg?style=flat-square
119 | [sponsors-image]: https://opencollective.com/imgcook/imove/sponsors/badge.svg?style=flat-square
120 | [gitter-image]: https://img.shields.io/gitter/room/i5ting/imove.svg?style=flat-square
121 | [gitter-url]: https://gitter.im/i5ting/imove?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge
122 | [#imgcook/imove]: https://webchat.freenode.net/?channels=#imgcook/imove
123 | [pr-welcoming-image]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square
124 | [pr-welcoming-url]: https://github.com/imgcook/imove/pull/new
125 |
126 | ## 项目 Star 数增长趋势
127 |
128 | [](https://starchart.cc/ykfe/imove)
129 |
130 |
--------------------------------------------------------------------------------
/__tests__/core/src/api/index.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/common/baseCell.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/common/const.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/common/previewCell.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/common/shortcuts.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/components/colorPicker.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/components/miniMapSimpleNode.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/components/xIcon.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/flowChart.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/header.test.jsx:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/layout.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/settingBar.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/sideBar.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/mods/toolBar.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/utils/flowChartUtils.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/__tests__/core/src/utils/index.test.js:
--------------------------------------------------------------------------------
1 | describe('test', () => {
2 | it('test suite', () => {
3 |
4 | })
5 | })
--------------------------------------------------------------------------------
/config/cli.jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | coverageDirectory: "coverage",
4 | coverageProvider: "v8",
5 | testEnvironment: "node",
6 | rootDir: '../',
7 | moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
8 | testMatch: [
9 | "/__tests__/cli/src/**/*.(spec|test).[jt]s?(x)"
10 | ]
11 | };
--------------------------------------------------------------------------------
/config/core.jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | coverageDirectory: "coverage",
4 | coverageProvider: "v8",
5 | testEnvironment: "node",
6 | rootDir: '../',
7 | moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
8 | testMatch: [
9 | "/__tests__/core/src/**/*.(spec|test).[jt]s?(x)"
10 | ]
11 | };
--------------------------------------------------------------------------------
/config/editor.jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | coverageDirectory: "coverage",
4 | coverageProvider: "v8",
5 | testEnvironment: "node",
6 | rootDir: '../',
7 | moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
8 | testMatch: [
9 | "/__tests__/json-schema-editor/src/**/*.(spec|test).[jt]s?(x)"
10 | ]
11 | };
--------------------------------------------------------------------------------
/config/plugin.jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | clearMocks: true,
3 | coverageDirectory: "coverage",
4 | coverageProvider: "v8",
5 | testEnvironment: "node",
6 | rootDir: '../',
7 | moduleFileExtensions: ['js', 'json', 'jsx', 'node'],
8 | testMatch: [
9 | "/__tests__/plugin-store/src/**/*.(spec|test).[jt]s?(x)"
10 | ]
11 | };
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/examples/configuration.test.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Actions', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:8000/')
6 | cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true })
7 | })
8 |
9 | function moveNodeAndSelect() {
10 | cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(0)
11 | .trigger('mousedown', { which: 1, force: true })
12 | .trigger('mousemove', { clientX: 400, clientY: 300, force: true })
13 | .trigger('mouseup', { force: true })
14 | cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(1).click({ force: true }) // 点击移动到画布中的节点
15 | }
16 |
17 | function assertEdit(nth, content) {
18 | moveNodeAndSelect()
19 | const ele = cy.get(`#rc-tabs-0-panel-basic > div > div:nth-child(${nth}) > button`).eq(0)
20 | ele.should('have.text', '编 辑')
21 | ele.click({ force: true })
22 | cy.get('.ant-modal-content').should('exist')
23 | cy.get('.ace_content').type(content)
24 | cy.get('button.ant-btn.ant-btn-primary').eq(0).click({ force: true })
25 | ele.click({ force: true })
26 | cy.get('.ace_content').should('contain', content)
27 | }
28 |
29 | it('修改节点名称', () => {
30 | moveNodeAndSelect()
31 | const ele = cy.get('#rc-tabs-0-panel-basic > div > div:nth-child(1) > input').eq(0)
32 | ele.clear({ force: true })
33 | ele.type('新的开始')
34 | ele.should('have.value', '新的开始')
35 | })
36 | it('修改节点代码', () => {
37 | assertEdit(2, 'console.log(1)')
38 | })
39 | it('修改投放配置schema', () => {
40 | assertEdit(3, `"a":1`)
41 | })
42 | it('修改npm依赖', () => {
43 | assertEdit(4, `"@ant-design/icons": "^4.0.6"`)
44 | })
45 | })
--------------------------------------------------------------------------------
/cypress/integration/examples/element.test.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | context('Actions', () => {
4 | beforeEach(() => {
5 | cy.visit('http://localhost:8000/')
6 | cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true })
7 | })
8 |
9 | function moveNode(selector, x, y) {
10 | cy.get(selector).eq(0)
11 | .trigger('mousedown', { which: 1, force: true })
12 | .trigger('mousemove', { clientX: x, clientY: y, force: true })
13 | .trigger('mouseup', { force: true })
14 | }
15 |
16 | // 节点/边操作测试
17 | it('添加 开始/分支/处理 节点', () => {
18 | const DEFAULT_COUNT = 1
19 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(1) > circle', 300, 150)
20 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(2) > polygon', 400, 150)
21 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(3) > rect', 500, 150)
22 | cy.get('svg> g > g.x6-graph-svg-stage circle').its('length').should('be.gt', DEFAULT_COUNT)
23 | cy.get('svg> g > g.x6-graph-svg-stage polygon').its('length').should('be.gt', DEFAULT_COUNT)
24 | cy.get('svg> g > g.x6-graph-svg-stage rect').its('length').should('be.gt', DEFAULT_COUNT)
25 | })
26 |
27 | it('边连线 & 删除节点/边', () => {
28 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(1) > circle', 300, 150)
29 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(2) > polygon', 400, 150)
30 | moveNode('svg > g > g.x6-graph-svg-stage > g:nth-child(3) > rect', 500, 150)
31 | const node1 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(1)').eq(1)
32 | const node2 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(2)').eq(1)
33 | const node3 = cy.get('svg > g > g.x6-graph-svg-stage > g:nth-child(3)').eq(1)
34 | node1.click('right', { force: true }).trigger('mousemove', { clientX: node2.clientX, clientY: node2.clientY, force: true }).trigger('mouseup', { force: true })
35 | node2.click('right', { force: true }).trigger('mousemove', { clientX: node3.clientX, clientY: node3.clientY, force: true }).trigger('mouseup', { force: true })
36 | cy.get('svg path:nth-child(1)').should('exist')
37 | cy.get('svg path:nth-child(1)').eq(1).click({ force: true })
38 | cy.get('body').type('{del}')
39 | cy.get('svg path:nth-child(1)').eq(1).click({ force: true })
40 | cy.get('body').type('{del}')
41 | })
42 | })
--------------------------------------------------------------------------------
/cypress/integration/examples/tools.test.js:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // 工具栏测试
4 | context('Actions', () => {
5 | const toolSelector = (i, j, k) => {
6 | if (k) {
7 | return `.index-module_container__1D841 > :nth-child(${i}) > :nth-child(${j})> :nth-child(${k})`
8 | } else {
9 | return `.index-module_container__1D841 > :nth-child(${i}) > :nth-child(${j})`
10 | }
11 | }
12 | beforeEach(() => {
13 | cy.visit('http://localhost:8000/')
14 | cy.get('div.ant-modal-confirm-btns > button:nth-child(1)').click({ force: true })
15 | })
16 |
17 | function moveNode(selector, x, y) {
18 | cy.get(selector).eq(0)
19 | .trigger('mousedown', { which: 1, force: true })
20 | .trigger('mousemove', { clientX: x, clientY: y, force: true })
21 | .trigger('mouseup', { force: true })
22 | }
23 |
24 | // 第一组
25 | it('保存', () => {
26 | const save = cy.get(toolSelector(1, 1))
27 | })
28 | it('适配窗口', () => {
29 | const fitWindow = cy.get(toolSelector(1, 2))
30 | })
31 | it('撤销', () => {
32 | const undo = cy.get(toolSelector(1, 3))
33 | const node = 'svg > g > g.x6-graph-svg-stage > g:nth-child(1)'
34 | moveNode(node, 350, 300)
35 | moveNode(node, 350, 300)
36 | undo.click({ multiple: true, force: true })
37 | undo.click({ multiple: true, force: true })
38 | cy.get(node).should('have.length', 2)
39 | })
40 | it('重做', () => {
41 | const redo = cy.get(toolSelector(1, 4))
42 | const node = 'svg > g > g.x6-graph-svg-stage > g:nth-child(1)'
43 | moveNode(node, 350, 300)
44 | redo.click()
45 | redo.click()
46 | cy.get(node).should('have.length', 2)
47 | })
48 |
49 | // 第二组
50 | it('缩小', () => {
51 | const zoomOut = cy.get(toolSelector(2, 1, 1))
52 | })
53 | it('放大', () => {
54 | const zoomIn = cy.get(toolSelector(2, 1, 2))
55 | })
56 |
57 | // 第三组
58 | it('修改字号', () => {
59 | const fontSize = cy.get(toolSelector(3, 1))
60 | })
61 | it('修改字重', () => {
62 | const fontWeight = cy.get(toolSelector(3, 2))
63 | })
64 | it('修改斜体', () => {
65 | const italic = cy.get(toolSelector(3, 3))
66 | })
67 | it('修改下划线', () => {
68 | const underline = cy.get(toolSelector(3, 4))
69 | })
70 |
71 | // 第四组
72 | it('修改文字颜色', () => {
73 | const textColor = cy.get(toolSelector(4, 1))
74 | })
75 | it('修改背景颜色', () => {
76 | const bgColor = cy.get(toolSelector(4, 2))
77 | })
78 | it('修改边框颜色', () => {
79 | const borderColor = cy.get(toolSelector(4, 3))
80 | })
81 | it('修改线条样式', () => {
82 | const lineStyle = cy.get(toolSelector(4, 4))
83 | })
84 |
85 | // 第五组
86 | it('修改水平方向对齐', () => {
87 | const align = cy.get(toolSelector(5, 1))
88 | })
89 | it('修改垂直方向对齐', () => {
90 | const vertical = cy.get(toolSelector(5, 2))
91 | })
92 |
93 | // 第六组
94 | it('修改层级置顶', () => {
95 | const top = cy.get(toolSelector(6, 1))
96 | })
97 | it('修改层级置底', () => {
98 | const bottom = cy.get(toolSelector(6, 2))
99 | })
100 | })
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | module.exports = (on, config) => {
19 | // `on` is used to hook into various events Cypress emits
20 | // `config` is the resolved Cypress config
21 | }
22 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/docs/api/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # 为iMove做贡献
2 |
3 | * 请先fork一份到自己的项目下,不要直接在仓库下建分支。
4 | * commit信息要以`[组件名]:描述信息`的形式填写,例如`[canvas]:fix xxx bug`。
5 | * 如果您已经修复了错误或者添加了应测试的代码,请添加测试!
6 | * 确保提交PR之前请先rebase,保持commit记录的整洁性。
7 | * 确保PR是提交到`dev`分支,而不是`master`分支。
8 | * 如果修复的是bug,请在PR中给出描述信息。
--------------------------------------------------------------------------------
/docs/api/ctx.md:
--------------------------------------------------------------------------------
1 | # 上下文(ctx)
--------------------------------------------------------------------------------
/docs/api/data.md:
--------------------------------------------------------------------------------
1 | # 数据读写(data)
--------------------------------------------------------------------------------
/docs/api/edge.md:
--------------------------------------------------------------------------------
1 | # 边(edge)
--------------------------------------------------------------------------------
/docs/api/group.md:
--------------------------------------------------------------------------------
1 | # 节点组(group)
--------------------------------------------------------------------------------
/docs/api/layout.md:
--------------------------------------------------------------------------------
1 | # 布局(layout)
--------------------------------------------------------------------------------
/docs/api/module.md:
--------------------------------------------------------------------------------
1 | # 逻辑元件(module)
--------------------------------------------------------------------------------
/docs/api/node.md:
--------------------------------------------------------------------------------
1 | # 节点(node)
--------------------------------------------------------------------------------
/docs/api/schema.md:
--------------------------------------------------------------------------------
1 | # 流程图(schema)
--------------------------------------------------------------------------------
/docs/config/index.md:
--------------------------------------------------------------------------------
1 | ## 配置文档
--------------------------------------------------------------------------------
/docs/docs/index.md:
--------------------------------------------------------------------------------
1 | ## 使用文档
--------------------------------------------------------------------------------
/docs/guide/index.md:
--------------------------------------------------------------------------------
1 | ## 问题指引
--------------------------------------------------------------------------------
/docs/plugins/index.md:
--------------------------------------------------------------------------------
1 | ## 插件文档
--------------------------------------------------------------------------------
/example/.env:
--------------------------------------------------------------------------------
1 | WATCH_IGNORED=node_modules/(?!(@imove/core)) umi dev
--------------------------------------------------------------------------------
/example/.umirc.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'umi';
2 |
3 | export default defineConfig({
4 | nodeModulesTransform: {
5 | type: 'none',
6 | },
7 | routes: [
8 | { path: '/', component: '@/pages/index' },
9 | ],
10 | });
11 |
--------------------------------------------------------------------------------
/example/mock/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i5ting/imove/0529e8127132380196ae14ea5f505b332dfcf8a6/example/mock/.gitkeep
--------------------------------------------------------------------------------
/example/mock/api.ts:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const low = require('lowdb');
3 | const FileSync = require('lowdb/adapters/FileSync');
4 |
5 | const adapter = new FileSync(path.join(__dirname, './db.json'));
6 | const db = low(adapter);
7 |
8 | const queryGraph = (req: any, res: any) => {
9 | const { projectId = 'default' } = req.body;
10 | const data = db.get(projectId).value() || [];
11 | res.send({ status: 200, code: 0, success: true, data: { cells: data } });
12 | };
13 |
14 | const modifyGraph = (req: any, res: any) => {
15 | const { projectId = 'default', actions = [] } = req.body;
16 | const projectData = db.get(projectId).value() || [];
17 | actions.forEach((action: any) => {
18 | const { data, actionType } = action;
19 | if (actionType === 'create') {
20 | projectData.push(data);
21 | } else if (actionType === 'update') {
22 | const foundIdx = projectData.findIndex((item: any) => item.id === data.id);
23 | if (foundIdx > -1) {
24 | projectData[foundIdx] = data;
25 | }
26 | } else if (actionType === 'remove') {
27 | const foundIdx = projectData.findIndex((item: any) => item.id === data.id);
28 | if (foundIdx > -1) {
29 | projectData.splice(foundIdx, 1);
30 | }
31 | }
32 | });
33 | db.set(projectId, projectData).write();
34 | res.send({ status: 200, code: 0, success: true, data: [] });
35 | };
36 |
37 |
38 | export default {
39 | 'POST /api/queryGraph': queryGraph,
40 | 'POST /api/modifyGraph': modifyGraph,
41 | };
42 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "example",
3 | "scripts": {
4 | "start": "umi dev",
5 | "build": "",
6 | "postinstall": "umi generate tmp"
7 | },
8 | "dependencies": {
9 | "@ant-design/pro-layout": "^6.5.0",
10 | "@imove/core": "^0.3.9",
11 | "@umijs/preset-react": "1.x",
12 | "lowdb": "^1.0.0",
13 | "umi": "^3.3.7"
14 | },
15 | "devDependencies": {
16 | "@types/react": "^16.9.0",
17 | "@types/react-dom": "^16.9.0"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/src/pages/index.less:
--------------------------------------------------------------------------------
1 |
2 | .normal {
3 | }
4 |
5 | .title {
6 | background: rgb(121, 242, 157);
7 | }
8 |
--------------------------------------------------------------------------------
/example/src/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import IMove from '@imove/core';
3 |
4 | const onSave = (data: { nodes: any; edges: any }): void => {
5 | console.log(data);
6 | };
7 |
8 | function Arrange(): JSX.Element {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default Arrange;
17 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "esnext",
5 | "moduleResolution": "node",
6 | "importHelpers": true,
7 | "jsx": "react",
8 | "esModuleInterop": true,
9 | "sourceMap": true,
10 | "baseUrl": "./",
11 | "strict": true,
12 | "paths": {
13 | "@/*": ["src/*"],
14 | "@@/*": ["src/.umi/*"]
15 | },
16 | "allowSyntheticDefaultImports": true
17 | },
18 | "include": [
19 | "mock/**/*",
20 | "src/**/*",
21 | "config/**/*",
22 | ".umirc.ts",
23 | "typings.d.ts"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/example/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.css';
2 | declare module '*.less';
3 | declare module "*.png";
4 | declare module '*.svg' {
5 | export function ReactComponent(props: React.SVGProps): React.ReactElement
6 | const url: string
7 | export default url
8 | }
9 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | "packages/*",
4 | "example"
5 | ],
6 | "version": "0.3.3"
7 | }
8 |
--------------------------------------------------------------------------------
/mlc_config.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignorePatterns": [
3 | {
4 | "pattern": "^https?://0.0.0.0:\\d+"
5 | },
6 | {
7 | "pattern": "^https?://127.0.0.1:\\d+"
8 | },
9 | {
10 | "pattern": "^https?://localhost:\\d+"
11 | }
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "root",
3 | "private": true,
4 | "scripts": {
5 | "test": "jest --coverage",
6 | "e2e": "cypress open",
7 | "doc": "dumi dev",
8 | "start": "concurrently \"lerna run watch --parallel\" \"cross-env APP_ROOT=example umi dev\"",
9 | "example": "concurrently \"lerna run watch --parallel\" \"cross-env APP_ROOT=example umi dev\" \"imove -d\"",
10 | "postinstall": "lerna init && lerna exec npm i && lerna exec npm run build && npm link packages/cli && lerna bootstrap",
11 | "prepare": "husky install"
12 | },
13 | "jest": {
14 | "projects": [
15 | "config/cli.jest.config.js",
16 | "config/core.jest.config.js",
17 | "config/editor.jest.config.js",
18 | "config/plugin.jest.config.js"
19 | ]
20 | },
21 | "devDependencies": {
22 | "@rollup/plugin-commonjs": "^11.0.2",
23 | "@rollup/plugin-json": "^4.0.3",
24 | "@rollup/plugin-node-resolve": "^7.1.3",
25 | "@rollup/plugin-strip": "^1.3.2",
26 | "@types/jest": "^26.0.16",
27 | "@typescript-eslint/eslint-plugin": "^4.14.0",
28 | "@typescript-eslint/parser": "^2.29.0",
29 | "concurrently": "^5.3.0",
30 | "cross-env": "^7.0.3",
31 | "cypress": "^6.2.0",
32 | "dumi": "^1.0.38",
33 | "eslint": "^6.8.0",
34 | "eslint-config-prettier": "^7.2.0",
35 | "eslint-plugin-prettier": "^3.3.1",
36 | "eslint-plugin-react": "^7.22.0",
37 | "husky": "^7.0.1",
38 | "lerna": "^3.22.1",
39 | "less": "^3.12.2",
40 | "lint-staged": "^11.0.1",
41 | "lowdb": "^1.0.0",
42 | "ora": "^4.1.1",
43 | "postcss": "^8.2.1",
44 | "prettier": "^2.2.1",
45 | "rollup": "^2.6.1",
46 | "rollup-plugin-postcss": "^4.0.0",
47 | "rollup-plugin-size-snapshot": "^0.11.0",
48 | "rollup-plugin-typescript": "^1.0.1",
49 | "snazzy": "^9.0.0",
50 | "ts-jest": "^26.4.4",
51 | "typescript": "^4.1.3",
52 | "umi": "^3.3.3",
53 | "watch": "^1.0.2"
54 | },
55 | "lint-staged": {
56 | "packages/**/*.{ts,tsx,js,jsx}": "eslint --cache --fix"
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/packages/cli/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/packages/cli/README.md:
--------------------------------------------------------------------------------
1 | ## iMove-cli
2 |
3 | ### Install
4 |
5 | ```bash
6 | npm i -g @imove/cli
7 | ```
8 |
9 | ### Usage
10 |
11 | - 初始化项目,创建 `imove.config.js`
12 |
13 | ```bash
14 | imove -i
15 | # OR
16 | imove --init
17 | ```
18 |
19 | - 启动开发服务及编辑器
20 |
21 | ```bash
22 | imove -de
23 | # OR
24 | imove --dev --editor
25 | ```
26 |
27 | 浏览器打开 [http://127.0.0.1:3500/](http://127.0.0.1:3500/)
28 |
--------------------------------------------------------------------------------
/packages/cli/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const path = require('path');
3 | const cmds = require('./src/cmd');
4 | const program = require('commander');
5 | const getConfig = require('./src/utils/getConfig');
6 | const pkg = require(path.join(__dirname, './package.json'));
7 |
8 | program
9 | .version(pkg.version)
10 | .option('-d, --dev', '本地开发')
11 | .option('-e, --editor', '开启编辑器')
12 | .option('-i, --init', '初始化配置文件')
13 | .parse(process.argv);
14 |
15 | Object.keys(cmds).forEach((cmd) => {
16 | const CmdCtor = cmds[cmd];
17 | if (program[cmd]) {
18 | const cmdInst = new CmdCtor({ config: getConfig() });
19 | cmdInst.run();
20 | }
21 | });
22 |
--------------------------------------------------------------------------------
/packages/cli/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@imove/cli",
3 | "version": "0.0.5",
4 | "description": "imove client",
5 | "main": "index.js",
6 | "bin": {
7 | "imove": "index.js"
8 | },
9 | "keywords": [
10 | "imove",
11 | "compile"
12 | ],
13 | "scripts": {
14 | "build": "",
15 | "build:editor": "node src/cmd/editor/build.js",
16 | "postinstall": "npm run build:editor"
17 | },
18 | "repository": {
19 | "type": "git",
20 | "url": "git+https://github.com/suanmei/iMove.git"
21 | },
22 | "author": "smallstonesk",
23 | "license": "MIT",
24 | "dependencies": {
25 | "@imove/compile-code": "^0.1.0",
26 | "@imove/core": "^0.3.9",
27 | "body-parser": "^1.19.0",
28 | "commander": "^6.0.0",
29 | "cors": "^2.8.5",
30 | "esbuild": "^0.12.15",
31 | "esbuild-plugin-less": "^1.0.7",
32 | "eventemitter3": "^4.0.7",
33 | "express": "^4.17.1",
34 | "fs-extra": "^9.0.1",
35 | "lowdb": "^1.0.0",
36 | "open": "^8.2.1"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/base/index.js:
--------------------------------------------------------------------------------
1 | class Base {
2 | constructor({ config }) {
3 | this._config = config;
4 | return this;
5 | }
6 |
7 | get config() {
8 | return this._config;
9 | }
10 |
11 | get projectPath() {
12 | return process.cwd();
13 | }
14 | }
15 |
16 | module.exports = Base;
17 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/dev/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const path = require('path');
3 | const fs = require('fs-extra');
4 | const Base = require('../base');
5 | const mergePkg = require('./mergePkg');
6 | const { compileForProject } = require('@imove/compile-code');
7 | const { DEFAULT_PORT, createServer } = require('../../utils/server');
8 |
9 | const noop = function () {
10 | /* noop function */
11 | };
12 | const CACHE_PATH = path.join(process.cwd(), './.cache');
13 | const CACHE_DSL_FILE = path.join(CACHE_PATH, 'imove.dsl.json');
14 |
15 | class Dev extends Base {
16 | async writeOutputIntoFiles(curPath, output) {
17 | for (const key in output) {
18 | const newPath = path.join(curPath, key);
19 | if (path.extname(newPath)) {
20 | await fs.writeFile(newPath, output[key]);
21 | } else {
22 | await fs.ensureDir(newPath);
23 | await this.writeOutputIntoFiles(newPath, output[key]);
24 | }
25 | }
26 | }
27 |
28 | async save(req, res) {
29 | const { outputPath, plugins = [] } = this.config;
30 |
31 | // check outputPath whether exsited
32 | await fs.ensureDir(outputPath);
33 |
34 | // check dsl whether existed
35 | if (!req.body || !req.body.dsl) {
36 | res.status(500).json({ isCompiled: false }).end();
37 | return;
38 | }
39 |
40 | // compile
41 | try {
42 | const { dsl } = req.body;
43 | const output = compileForProject(dsl, plugins);
44 | await this.writeOutputIntoFiles(outputPath, output);
45 | await mergePkg(dsl, this.projectPath);
46 | await fs.outputFile(CACHE_DSL_FILE, JSON.stringify(dsl, null, 2));
47 | res.status(200).json({ isCompiled: true }).end();
48 | console.log('compile successfully!');
49 | } catch (err) {
50 | res.status(500).json({ isCompiled: false }).end();
51 | console.log('compile failed! the error is:', err);
52 | }
53 | }
54 |
55 | async connect(req, res) {
56 | const { projectName } = this.config;
57 | const dsl = await fs.readJson(CACHE_DSL_FILE).catch(noop);
58 | res.status(200).json({ projectName, dsl }).end();
59 | }
60 |
61 | run() {
62 | const app = createServer(DEFAULT_PORT);
63 | app.post('/api/save', this.save.bind(this));
64 | app.get('/api/connect', this.connect.bind(this));
65 | }
66 | }
67 |
68 | module.exports = Dev;
69 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/dev/mergePkg.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs-extra');
3 |
4 | const builtinDependencies = {
5 | eventemitter3: '^4.0.7',
6 | };
7 |
8 | const extractDep = (dsl) => {
9 | const mergedDependencies = {};
10 | const { cells = [] } = dsl;
11 | cells
12 | .filter((cell) => cell.shape !== 'edge')
13 | .forEach((cell) => {
14 | const { dependencies } = cell.data || {};
15 | try {
16 | const json = JSON.parse(dependencies);
17 | Object.keys(json).forEach(
18 | (key) => (mergedDependencies[key] = json[key]),
19 | );
20 | } catch (error) {
21 | console.log(
22 | 'extract dependencies failed, the error is:',
23 | error.message,
24 | );
25 | }
26 | });
27 | return mergedDependencies;
28 | };
29 |
30 | const setup = async (dsl, projectRootPath) => {
31 | const pkgPath = path.join(projectRootPath, './package.json');
32 | const pkgFile = await fs.readFile(pkgPath);
33 | const pkgJson = JSON.parse(pkgFile);
34 | const dslDependencies = extractDep(dsl);
35 | if (!pkgJson.dependencies) {
36 | pkgJson.dependencies = dslDependencies;
37 | } else {
38 | Object.keys(dslDependencies).forEach((key) => {
39 | pkgJson.dependencies[key] = dslDependencies[key];
40 | });
41 | Object.keys(builtinDependencies).forEach((key) => {
42 | pkgJson.dependencies[key] = builtinDependencies[key];
43 | });
44 | }
45 | await fs.writeFile(pkgPath, JSON.stringify(pkgJson, null, 2));
46 | };
47 |
48 | module.exports = setup;
49 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/editor/build.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /* eslint-disable @typescript-eslint/no-var-requires */
4 | const path = require('path');
5 | const { lessLoader } = require('esbuild-plugin-less');
6 |
7 | // build js and css for editor
8 | const OUT_FILE = path.join(__dirname, 'template/dist/app.bundle.js');
9 | require('esbuild')
10 | .build({
11 | bundle: true,
12 | entryPoints: [path.join(__dirname, 'template/app.jsx')],
13 | outfile: OUT_FILE,
14 | plugins: [
15 | // fix multi-react with lerna
16 | {
17 | name: 'resolve-multi-react',
18 | setup: (build) => {
19 | build.onResolve({ filter: /^react$/, namespace: 'file' }, () => {
20 | return {
21 | path: require.resolve('react'),
22 | watchFiles: undefined,
23 | };
24 | });
25 | },
26 | },
27 | // fix import('antd/dist/antd.less')
28 | {
29 | name: 'resolve-antd-dist-less',
30 | setup: (build) => {
31 | build.onResolve(
32 | { filter: /antd\/dist\/antd\.less$/, namespace: 'file' },
33 | () => {
34 | return {
35 | path: '',
36 | watchFiles: undefined,
37 | };
38 | },
39 | );
40 | },
41 | },
42 | // less
43 | lessLoader({
44 | javascriptEnabled: true,
45 | }),
46 | ],
47 | })
48 | .then(() => {
49 | console.log('imove editor builded');
50 | })
51 | .catch(() => process.exit(1));
52 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/editor/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const fs = require('fs-extra');
3 | const path = require('path');
4 | const low = require('lowdb');
5 | const FileSync = require('lowdb/adapters/FileSync');
6 | const express = require('express');
7 | const Base = require('../base');
8 | const { DEFAULT_PORT, createServer } = require('../../utils/server');
9 |
10 | class Editor extends Base {
11 | constructor(...args) {
12 | super(...args);
13 | if (!this.dbFile) {
14 | fs.ensureDirSync(this.outputPath);
15 | this.dbFile = path.join(this.outputPath, 'db.json');
16 | }
17 | // make sure dbFile existed
18 | fs.createFileSync(this.dbFile);
19 |
20 | const adapter = new FileSync(this.dbFile);
21 | this.db = low(adapter);
22 | }
23 |
24 | get projectName() {
25 | const { projectName = 'default' } = this.config || '';
26 | return projectName;
27 | }
28 |
29 | get outputPath() {
30 | const { outputPath } = this.config || '';
31 | return outputPath;
32 | }
33 |
34 | get dbFile() {
35 | const dbFile = this._dbFile || (this.config || '').dbFile;
36 | return dbFile;
37 | }
38 |
39 | set dbFile(val) {
40 | this._dbFile = val;
41 | }
42 |
43 | queryGraph(req, res) {
44 | const data = this.db.get(this.projectName).value() || [];
45 | res.send({ status: 200, code: 0, success: true, data: { cells: data } });
46 | }
47 |
48 | modifyGraph(req, res) {
49 | const { actions = [] } = req.body;
50 | const projectData = this.db.get(this.projectName).value() || [];
51 | actions.forEach((action) => {
52 | const { data, actionType } = action;
53 | if (actionType === 'create') {
54 | projectData.push(data);
55 | } else if (actionType === 'update') {
56 | const foundIdx = projectData.findIndex((item) => item.id === data.id);
57 | if (foundIdx > -1) {
58 | projectData[foundIdx] = data;
59 | }
60 | } else if (actionType === 'remove') {
61 | const foundIdx = projectData.findIndex((item) => item.id === data.id);
62 | if (foundIdx > -1) {
63 | projectData.splice(foundIdx, 1);
64 | }
65 | }
66 | });
67 | this.db.set(this.projectName, projectData).write();
68 | res.send({ status: 200, code: 0, success: true, data: [] });
69 | }
70 |
71 | run() {
72 | const server = createServer(DEFAULT_PORT, true);
73 | server.use(express.static(path.join(__dirname, './template')));
74 | server.post('/api/queryGraph', this.queryGraph.bind(this));
75 | server.post('/api/modifyGraph', this.modifyGraph.bind(this));
76 | }
77 | }
78 |
79 | module.exports = Editor;
80 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/editor/template/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import IMove from '@imove/core';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/editor/template/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | iMove Editor
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/index.js:
--------------------------------------------------------------------------------
1 | const Dev = require('./dev');
2 | const Init = require('./init');
3 | const Editor = require('./editor');
4 |
5 | module.exports = {
6 | dev: Dev,
7 | init: Init,
8 | editor: Editor,
9 | };
10 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/init/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const path = require('path');
3 | const fs = require('fs-extra');
4 | const Base = require('../base');
5 |
6 | const CONFIG_TPL_PATH = path.join(__dirname, './template/imove.config.js.tpl');
7 | const OUTPUT_FILE_PATH = path.join(process.cwd(), 'imove.config.js');
8 |
9 | class Init extends Base {
10 | getProjectName() {
11 | const pkgPath = path.join(this.projectPath, 'package.json');
12 | const pkgJson = fs.readJSONSync(pkgPath);
13 | return pkgJson.name;
14 | }
15 |
16 | getOutputContent() {
17 | const tplContent = fs.readFileSync(CONFIG_TPL_PATH, 'utf-8');
18 | return tplContent.replace('{projectName}', `'${this.getProjectName()}'`);
19 | }
20 |
21 | run() {
22 | fs.outputFileSync(OUTPUT_FILE_PATH, this.getOutputContent());
23 | console.log(`Create ${OUTPUT_FILE_PATH} at successfully!`);
24 | }
25 | }
26 |
27 | module.exports = Init;
28 |
--------------------------------------------------------------------------------
/packages/cli/src/cmd/init/template/imove.config.js.tpl:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | projectName: {projectName},
5 | outputPath: path.join(__dirname, './src/logic')
6 | };
7 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/getConfig.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const fs = require('fs');
3 |
4 | const CONFIG_FILE_NAME = 'imove.config.js';
5 | const CONFIG_FILE_PATH = path.join(process.cwd(), CONFIG_FILE_NAME);
6 | const DEFAULT_CONFIG = {
7 | outputPath: path.join(process.cwd(), './src/logic/'),
8 | };
9 | const mergeConfig = (config, DEFAULT_CONFIG) => {
10 | // TODO: merge config
11 | return config;
12 | };
13 |
14 | const getConfig = () => {
15 | const isConfigFileExisted = fs.existsSync(CONFIG_FILE_PATH);
16 | if (isConfigFileExisted) {
17 | return mergeConfig(require(CONFIG_FILE_PATH), DEFAULT_CONFIG);
18 | } else {
19 | return DEFAULT_CONFIG;
20 | }
21 | };
22 |
23 | module.exports = getConfig;
24 |
--------------------------------------------------------------------------------
/packages/cli/src/utils/server/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/no-var-requires */
2 | const express = require('express');
3 | const bodyParser = require('body-parser');
4 | const open = require('open');
5 |
6 | const DEFAULT_PORT = 3500;
7 | const cachedServer = {};
8 |
9 | function openBrowserIfNeeded(url, needOpen = false) {
10 | if (needOpen) {
11 | open(url);
12 | }
13 | }
14 |
15 | const createServer = (port = DEFAULT_PORT, needOpen = false) => {
16 | const url = `http://127.0.0.1:${port}`;
17 | if (cachedServer[port]) {
18 | openBrowserIfNeeded(url, needOpen);
19 | return cachedServer[port];
20 | }
21 | const app = express();
22 | cachedServer[port] = app;
23 |
24 | app.use(bodyParser.json({ limit: '50mb' }));
25 | app.use(bodyParser.urlencoded({ extended: false }));
26 | app.use((req, res, next) => {
27 | res.header('Access-Control-Allow-Origin', '*');
28 | res.header(
29 | 'Access-Control-Allow-Headers',
30 | 'Origin, X-Requested-With, Content-Type, Accept',
31 | );
32 | next();
33 | });
34 | app.listen(port, () => {
35 | console.log(`server starts at ${url}`);
36 | openBrowserIfNeeded(url, needOpen);
37 | });
38 | return app;
39 | };
40 |
41 | module.exports = {
42 | DEFAULT_PORT,
43 | createServer,
44 | };
45 |
--------------------------------------------------------------------------------
/packages/compile-code/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/packages/compile-code/README.md:
--------------------------------------------------------------------------------
1 | # @imove/compile-code
--------------------------------------------------------------------------------
/packages/compile-code/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@imove/compile-code",
3 | "version": "0.1.0",
4 | "description": "imove compile code",
5 | "main": "dist/core.common.js",
6 | "module": "dist/core.esm.js",
7 | "types": "dist/types/index.d.ts",
8 | "directories": {
9 | "dist": "dist"
10 | },
11 | "files": [
12 | "dist"
13 | ],
14 | "publishConfig": {
15 | "access": "public",
16 | "registry": "http://registry.npmjs.org/"
17 | },
18 | "keywords": [
19 | "imove",
20 | "compile code"
21 | ],
22 | "scripts": {
23 | "prepublishOnly": "node scripts/prepublish.js",
24 | "declare-type": "tsc --emitDeclarationOnly",
25 | "build": "rollup -c & npm run declare-type",
26 | "watch": "watch \"npm run build\" ./src"
27 | },
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/imgcook/iMove.git"
31 | },
32 | "author": "smallstonesk",
33 | "license": "MIT",
34 | "dependencies": {
35 | "@antv/x6": "^1.5.1"
36 | },
37 | "devDependencies": {}
38 | }
39 |
--------------------------------------------------------------------------------
/packages/compile-code/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import rollupBaseConfig from '../../rollup.config';
3 | import typescript from 'rollup-plugin-typescript';
4 | import pkg from './package.json';
5 |
6 | export default Object.assign(rollupBaseConfig, {
7 | input: path.join(__dirname, './src/index.ts'),
8 | output: [
9 | {
10 | file: pkg.main,
11 | format: 'cjs',
12 | },
13 | {
14 | file: pkg.module,
15 | format: 'es',
16 | },
17 | ],
18 | plugins: [
19 | typescript(),
20 | ]
21 | });
22 |
--------------------------------------------------------------------------------
/packages/compile-code/scripts/prepublish.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const path = require('path');
4 | const prePublish = require('../../../scripts/prepublish');
5 |
6 | prePublish('@imove/compile-code', path.join(__dirname, '../'));
7 |
--------------------------------------------------------------------------------
/packages/compile-code/src/addPlugins.ts:
--------------------------------------------------------------------------------
1 | const INSERT_IMPORT_PLUGINS_COMMENT = '// import plugins here';
2 | const INSERT_USE_PLUGINS_COMMENT = '// use plugins here';
3 |
4 | const addPlugins = (originalCode = '', plugins: string[] = []): string => {
5 | const modifiedContent: string = originalCode
6 | .replace(new RegExp(INSERT_IMPORT_PLUGINS_COMMENT), () => {
7 | return plugins
8 | .map((plugin, index) => `import plugin${index} from '${plugin}';`)
9 | .join('\n');
10 | })
11 | .replace(new RegExp(INSERT_USE_PLUGINS_COMMENT), () => {
12 | return plugins.map((_, index) => `logic.use(plugin${index});`).join('\n');
13 | });
14 | return modifiedContent;
15 | };
16 |
17 | export default addPlugins;
18 |
--------------------------------------------------------------------------------
/packages/compile-code/src/compileForOnline.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from '@antv/x6';
2 | import makeCode from './template/runOnline';
3 | import simplifyDSL from './simplifyDSL';
4 |
5 | interface DSL {
6 | cells: Cell.Properties[];
7 | }
8 |
9 | /**
10 | * Solution
11 | *
12 | * 1. find the source node form dsl, and if it is not imove-start,
13 | * then insert a vitural imove-start at first.
14 | *
15 | * 2. transform node funciton, follows should be noted:
16 | * - import statement should be replaced with import('packge/from/network')
17 | * - export statement should be replace with return function
18 | * - each node function should be wrapped within a new function to avoid duplicate declaration global variable
19 | *
20 | * 3. assemble Logic, Context, simplyfied dsl and nodeFns map into one file
21 | *
22 | */
23 |
24 | const INSERT_DSL_COMMENT = '// define dsl here';
25 | const INSERT_NODE_FNS_COMMENT = '// define nodeFns here';
26 | const importRegex = /import\s([\s\S]*?)\sfrom\s('|")((@\w[\w\.\-]+\/)?(\w[\w\.\-\/]+))\2/gm;
27 | const virtualSourceNode = {
28 | id: 'virtual-imove-start',
29 | shape: 'imove-start',
30 | data: {
31 | trigger: 'virtual-imove-start',
32 | configData: {},
33 | code: 'export default async function(ctx) {\n \n}',
34 | },
35 | };
36 |
37 | const findStartNode = (dsl: DSL): Cell.Properties => {
38 | const nodes = dsl.cells.filter((cell) => cell.shape !== 'edge');
39 | const edges = dsl.cells.filter((cell) => cell.shape === 'edge');
40 |
41 | if (nodes.length === 0) {
42 | throw new Error('Compile failed, no node is selected');
43 | }
44 |
45 | let foundEdge = null;
46 | let startNode = nodes[0];
47 | while (
48 | (foundEdge = edges.find((edge) => edge.target.cell === startNode.id))
49 | ) {
50 | const newSourceId = foundEdge.source.cell;
51 | startNode = nodes.find(
52 | (node) => node.id === newSourceId,
53 | ) as Cell.Properties;
54 | }
55 |
56 | if (startNode.shape !== 'imove-start') {
57 | dsl.cells.push(virtualSourceNode, {
58 | shape: 'edge',
59 | source: {
60 | cell: 'virtual-imove-start',
61 | },
62 | target: {
63 | cell: startNode.id,
64 | },
65 | });
66 | startNode = virtualSourceNode;
67 | }
68 |
69 | return startNode;
70 | };
71 |
72 | const getNextNode = (curNode: Cell.Properties, dsl: DSL) => {
73 | const nodes = dsl.cells.filter((cell) => cell.shape !== 'edge');
74 | const edges = dsl.cells.filter((cell) => cell.shape === 'edge');
75 |
76 | const foundEdge = edges.find((edge) => edge.source.cell === curNode.id);
77 | if (foundEdge) {
78 | return nodes.find((node) => node.id === foundEdge.target.cell);
79 | }
80 | };
81 |
82 | const compileSimplifiedDSL = (dsl: DSL): string => {
83 | const simplyfiedDSL = JSON.stringify(simplifyDSL(dsl), null, 2);
84 | return `const dsl = ${simplyfiedDSL};`;
85 | };
86 |
87 | const compileNodeFn = (node: Cell.Properties): string => {
88 | const {
89 | data: { label, code },
90 | } = node;
91 | const newCode = code
92 | .replace(
93 | importRegex,
94 | (match: string, p1: string, p2: string, p3: string) => {
95 | return `const ${p1} = (await import('https://jspm.dev/${p3}')).default;`;
96 | },
97 | )
98 | .replace(/export\s+default/, 'return');
99 |
100 | return `await (async function() {
101 | ${newCode}
102 | }())`;
103 | };
104 |
105 | const compileNodeFnsMap = (dsl: DSL): string => {
106 | const nodes = dsl.cells.filter((cell) => cell.shape !== 'edge');
107 | const kvs = nodes.map((node) => {
108 | const { id } = node;
109 | return `'${id}': ${compileNodeFn(node)}`;
110 | });
111 |
112 | return `const nodeFns = {\n ${kvs.join(',\n ')}\n}`;
113 | };
114 |
115 | const compile = (dsl: DSL, mockInput: any): string => {
116 | const startNode = findStartNode(dsl);
117 | const mockNode = getNextNode(startNode, dsl);
118 | return makeCode(mockNode, mockInput)
119 | .replace(INSERT_DSL_COMMENT, compileSimplifiedDSL(dsl))
120 | .replace(INSERT_NODE_FNS_COMMENT, compileNodeFnsMap(dsl))
121 | .replace('$TRIGGER$', startNode.data.trigger);
122 | };
123 |
124 | export default compile;
125 |
--------------------------------------------------------------------------------
/packages/compile-code/src/compileForProject.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from '@antv/x6';
2 | import addPlugins from './addPlugins';
3 | import simplifyDSL from './simplifyDSL';
4 | import extractNodeFns from './extractNodeFns';
5 | import logicTpl from './template/logic';
6 | import indexTpl from './template/index';
7 | import contextTpl from './template/context';
8 |
9 | interface DSL {
10 | cells: Cell.Properties[];
11 | }
12 |
13 | interface IOutput {
14 | nodeFns: {
15 | [fileName: string]: string;
16 | };
17 | 'context.js': string;
18 | 'dsl.json': string;
19 | 'index.js': string;
20 | 'logic.js': string;
21 | }
22 |
23 | const compile = (dsl: DSL, plugins = []): IOutput => {
24 | const output: IOutput = {
25 | nodeFns: extractNodeFns(dsl),
26 | 'context.js': contextTpl,
27 | 'dsl.json': JSON.stringify(simplifyDSL(dsl), null, 2),
28 | 'index.js': addPlugins(indexTpl, plugins),
29 | 'logic.js': logicTpl,
30 | };
31 | return output;
32 | };
33 |
34 | export default compile;
35 |
--------------------------------------------------------------------------------
/packages/compile-code/src/extractNodeFns.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from '@antv/x6';
2 |
3 | interface DSL {
4 | cells: Cell.Properties[];
5 | }
6 |
7 | interface INodesFns {
8 | [fileName: string]: string;
9 | }
10 |
11 | const genEntryFile = (nodeIds: string[]): string => {
12 | const imports: string[] = [];
13 | const funcMaps: string[] = [];
14 | nodeIds.forEach((id, idx) => {
15 | const funcName = `fn_${idx}`;
16 | imports.push(`import ${funcName} from './${id}';`);
17 | funcMaps.push(`'${id}': ${funcName}`);
18 | });
19 | const fileContent: string = [
20 | imports.join('\n'),
21 | `const nodeFns = {\n ${funcMaps.join(',\n ')}\n};`,
22 | 'export default nodeFns;',
23 | ].join('\n');
24 | return fileContent;
25 | };
26 |
27 | const genNodeFns = (dsl: DSL): INodesFns => {
28 | const nodeFns: INodesFns = {};
29 | const { cells = [] } = dsl;
30 | const nodes = cells.filter((cell) => cell.shape !== 'edge');
31 | for (const {
32 | id,
33 | shape,
34 | data: { label, code },
35 | } of nodes) {
36 | const fileName: string = id + '.js';
37 | const descData = `// ${shape}: ${label}\n`;
38 | const saveData = `${descData}\n${code}`;
39 | nodeFns[fileName] = saveData;
40 | }
41 | return nodeFns;
42 | };
43 |
44 | const extract = (dsl: DSL): INodesFns => {
45 | const nodeFns = genNodeFns(dsl);
46 | const nodeIds = Object.keys(nodeFns).map((fileName) => fileName.slice(0, -3));
47 | const entryFileContent = genEntryFile(nodeIds);
48 | nodeFns['index.js'] = entryFileContent;
49 | return nodeFns;
50 | };
51 |
52 | export default extract;
53 |
--------------------------------------------------------------------------------
/packages/compile-code/src/index.ts:
--------------------------------------------------------------------------------
1 | import compileForOnline from './compileForOnline';
2 | import compileForProject from './compileForProject';
3 |
4 | export { compileForOnline, compileForProject };
5 |
--------------------------------------------------------------------------------
/packages/compile-code/src/simplifyDSL.ts:
--------------------------------------------------------------------------------
1 | import { Cell } from '@antv/x6';
2 |
3 | interface DSL {
4 | cells: Cell.Properties[];
5 | }
6 |
7 | const extractObj = (
8 | obj: Cell.Properties = {},
9 | keys: string[] = [],
10 | ): Cell.Properties => {
11 | const ret: Cell.Properties = {};
12 | keys.forEach((key) => {
13 | if (obj[key]) {
14 | ret[key] = obj[key];
15 | }
16 | });
17 | return ret;
18 | };
19 |
20 | const simplifyDSL = (dsl: DSL): Cell.Properties => {
21 | const { cells = [] } = dsl;
22 | return {
23 | cells: cells.map((cell) => {
24 | if (cell.shape === 'edge') {
25 | return extractObj(cell, ['id', 'shape', 'source', 'target']);
26 | } else {
27 | const newCell = extractObj(cell, ['id', 'shape', 'data']);
28 | newCell.data = extractObj(cell.data, [
29 | 'trigger',
30 | 'configData',
31 | 'ports',
32 | ]);
33 | return newCell;
34 | }
35 | }),
36 | };
37 | };
38 |
39 | export default simplifyDSL;
40 |
--------------------------------------------------------------------------------
/packages/compile-code/src/template/context.ts:
--------------------------------------------------------------------------------
1 | export default `export default class Context {
2 | constructor(opts) {
3 | this._init(opts);
4 | }
5 |
6 | _init(opts = {}) {
7 | const { payload = {} } = opts;
8 | this.curNode = null;
9 | this.context = {};
10 | this.payload = Object.freeze({ ...payload });
11 | }
12 |
13 | _transitTo(node, lastRet) {
14 | this.curNode = node;
15 | this.lastRet = lastRet;
16 | }
17 |
18 | getConfig() {
19 | return this.curNode.data.configData;
20 | }
21 |
22 | getPayload() {
23 | return this.payload;
24 | }
25 |
26 | getPipe() {
27 | return this.lastRet;
28 | }
29 |
30 | getContext() {
31 | return this.context;
32 | }
33 |
34 | setContext(data = {}) {
35 | Object.keys(data).forEach((key) => {
36 | this.context[key] = data[key];
37 | });
38 | }
39 | }
40 | `;
41 |
--------------------------------------------------------------------------------
/packages/compile-code/src/template/index.ts:
--------------------------------------------------------------------------------
1 | export default `import Logic from './logic';
2 | import dsl from './dsl.json';
3 | // import plugins here
4 |
5 | const logic = new Logic({ dsl });
6 |
7 | // use plugins here
8 |
9 | export default logic;
10 | `;
11 |
--------------------------------------------------------------------------------
/packages/compile-code/src/template/runOnline.ts:
--------------------------------------------------------------------------------
1 | import logic from './logic';
2 | import context from './context';
3 |
4 | const makeCode = (mockNode: any, mockInput: any) => `
5 | (async function run() {
6 | // Context
7 | ${context.replace(/export\s+default/, '')}
8 |
9 | // Logic
10 | ${logic
11 | .split('\n')
12 | .filter(
13 | (line) => !line.match(/import nodeFns/) && !line.match(/import Context/),
14 | )
15 | .join('\n')
16 | .replace(/export\s+default/, '')
17 | .replace(
18 | `import EventEmitter from 'eventemitter3';`,
19 | `const EventEmitter = (await import('https://jspm.dev/eventemitter3')).default;`,
20 | )}
21 |
22 | // DSL
23 | // define dsl here
24 |
25 | // nodeFns map
26 | // define nodeFns here
27 |
28 | // imove plugin
29 | const mockPlugin = () => {
30 | const mockNode = ${JSON.stringify(mockNode)};
31 | const mockInput = ${JSON.stringify(mockInput)};
32 | const toMockTargets = [
33 | ['pipe', 'getPipe'],
34 | ['config', 'getConfig'],
35 | ['payload', 'getPayload'],
36 | ['context', 'getContext'],
37 | ];
38 | return {
39 | enterNode(ctx) {
40 | // hijack
41 | if(ctx.curNode.id === mockNode.id) {
42 | toMockTargets.forEach(item => {
43 | const [type, method] = item;
44 | item[2] = ctx[method];
45 | ctx[method] = () => mockInput[type];
46 | });
47 | }
48 | },
49 | leaveNode(ctx) {
50 | // restore
51 | if(ctx.curNode.id === mockNode.id) {
52 | toMockTargets.forEach(item => {
53 | const [type, method, originMethod] = item;
54 | ctx[method] = originMethod;
55 | });
56 | }
57 | }
58 | };
59 | };
60 |
61 | // instantiation and invoke
62 | const logic = new Logic({ dsl });
63 | logic.use(mockPlugin);
64 | logic.invoke('$TRIGGER$', {}, (pipe) => {
65 | const ctx = logic._getUnsafeCtx();
66 | const context = ctx.getContext();
67 | window.dispatchEvent(new CustomEvent('iMoveOnlineExecEnds', {detail: {pipe, context}}));
68 | });
69 | })().catch(err => {
70 | console.error(err.message);
71 | window.dispatchEvent(new CustomEvent('iMoveOnlineExecEnds', {detail: {error: {message: err.message}}}));
72 | });
73 | `;
74 |
75 | export default makeCode;
76 |
--------------------------------------------------------------------------------
/packages/compile-code/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "dist/types"
5 | },
6 | "include": ["./src", "./types"]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | src
3 |
--------------------------------------------------------------------------------
/packages/core/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
--------------------------------------------------------------------------------
/packages/core/README.md:
--------------------------------------------------------------------------------
1 | # `core`
2 |
3 | > TODO: description
4 |
5 | ## Usage
6 |
7 | ```
8 | const core = require('core');
9 |
10 | // TODO: DEMONSTRATE API
11 | ```
12 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@imove/core",
3 | "version": "0.3.9",
4 | "description": "流程编排",
5 | "keywords": [
6 | "imove",
7 | "x6",
8 | "flowchart"
9 | ],
10 | "author": "suanmei ",
11 | "homepage": "https://github.com/suanmei/iMove#readme",
12 | "license": "MIT",
13 | "main": "dist/core.common.js",
14 | "module": "dist/core.esm.js",
15 | "types": "dist/types/index.d.ts",
16 | "directories": {
17 | "dist": "dist"
18 | },
19 | "files": [
20 | "dist"
21 | ],
22 | "publishConfig": {
23 | "access": "public"
24 | },
25 | "repository": {
26 | "type": "git",
27 | "url": "git+https://github.com/suanmei/iMove.git"
28 | },
29 | "scripts": {
30 | "prepublishOnly": "node scripts/prepublish.js",
31 | "declare-type": "tsc --emitDeclarationOnly",
32 | "build": "rollup -c & npm run declare-type",
33 | "watch": "watch \"npm run build\" ./src"
34 | },
35 | "bugs": {
36 | "url": "https://github.com/suanmei/iMove/issues"
37 | },
38 | "dependencies": {
39 | "@ant-design/icons": "^4.0.6",
40 | "@antv/x6": "^1.7.12",
41 | "@imove/compile-code": "^0.1.0",
42 | "@monaco-editor/react": "^3.7.4",
43 | "antd": "^4.5.3",
44 | "axios": "^0.21.0",
45 | "fr-generator": "^1.0.0",
46 | "jszip": "^3.5.0",
47 | "lodash.merge": "^4.6.2",
48 | "query-string": "^6.13.7",
49 | "react-color": "^2.18.1",
50 | "react-json-view": "^1.19.1",
51 | "react-split-pane-v2": "^1.0.3"
52 | },
53 | "devDependencies": {
54 | "@types/lodash.merge": "^4.6.6",
55 | "@types/react": "^16.9.34",
56 | "@types/react-color": "^3.0.4",
57 | "@types/react-dom": "^16.9.7"
58 | },
59 | "peerDependencies": {
60 | "@ant-design/icons": "^4.0.6",
61 | "@antv/x6": "0.10.59",
62 | "antd": "^4.5.3",
63 | "react": "^16.13.1",
64 | "react-dom": "^16.13.1"
65 | },
66 | "gitHead": "60b114ff262513544204f33a9bbcb6220d6c1c5d"
67 | }
68 |
--------------------------------------------------------------------------------
/packages/core/rollup.config.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json';
2 | import postcss from 'rollup-plugin-postcss';
3 | import commonjs from '@rollup/plugin-commonjs';
4 | import typescript from 'rollup-plugin-typescript';
5 | import rollupBaseConfig from '../../rollup.config';
6 |
7 | export default Object.assign(rollupBaseConfig, {
8 | plugins: [
9 | postcss(),
10 | typescript(),
11 | commonjs()
12 | ],
13 | output: [
14 | {
15 | file: pkg.main,
16 | format: 'cjs',
17 | },
18 | {
19 | file: pkg.module,
20 | format: 'es',
21 | },
22 | ],
23 | external: Object.keys(pkg.peerDependencies),
24 | });
25 |
--------------------------------------------------------------------------------
/packages/core/scripts/prepublish.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const path = require('path');
4 | const prePublish = require('../../../scripts/prepublish');
5 |
6 | prePublish('@imove/core', path.join(__dirname, '../'));
7 |
--------------------------------------------------------------------------------
/packages/core/src/api/index.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { message } from 'antd';
3 |
4 | const LOCAL_CONFIG_KEY = 'IMOVE:LOCAL_CONFIG_KEY';
5 | export interface ILocalConfig {
6 | ip: string;
7 | port: string;
8 | npmRegistry: string;
9 | }
10 |
11 | export enum ActionType {
12 | create = 'create',
13 | update = 'update',
14 | remove = 'remove',
15 | }
16 |
17 | export interface IModifyGraphAction {
18 | type: string;
19 | actionType: ActionType;
20 | data: any;
21 | }
22 |
23 | interface RequestConfig {
24 | url: string;
25 | method?: 'get' | 'post';
26 | params?: { [key: string]: any };
27 | headers?: { [key: string]: string };
28 | }
29 |
30 | const request = (function () {
31 | const instance = axios.create();
32 | instance.interceptors.response.use((response: any) => {
33 | const { data } = response || {};
34 | const { success, msg } = data || {};
35 | if (success) {
36 | return data;
37 | } else {
38 | message.error(msg);
39 | return Promise.reject(data);
40 | }
41 | });
42 | return (config: RequestConfig) => {
43 | const { url, method = 'post', params, headers = {} } = config;
44 | return instance.request({
45 | url,
46 | method,
47 | headers: {
48 | 'Content-Type': 'application/json;charset=utf-8',
49 | ...headers,
50 | },
51 | data: params,
52 | timeout: 3000,
53 | });
54 | };
55 | })();
56 |
57 | /**
58 | * get local config data (saved in localStorage)
59 | * @returns local config
60 | */
61 | export const getLocalConfig = (): ILocalConfig => {
62 | const savedConfigString = localStorage.getItem(LOCAL_CONFIG_KEY) || '';
63 | let savedConfig = {} as ILocalConfig;
64 | try {
65 | savedConfig = JSON.parse(savedConfigString);
66 | } catch (e) {}
67 | return {
68 | ip: savedConfig.ip || '127.0.0.1',
69 | port: savedConfig.port || '3500',
70 | npmRegistry: savedConfig.npmRegistry || 'https://registry.npm.taobao.org',
71 | };
72 | };
73 |
74 | /**
75 | * get local config data (saved in localStorage)
76 | */
77 | export const updateLocalConfig = (config: ILocalConfig) => {
78 | const savedConfig = getLocalConfig();
79 | savedConfig.ip = config.ip || savedConfig.ip;
80 | savedConfig.port = config.port || savedConfig.port;
81 | savedConfig.npmRegistry = (
82 | config.npmRegistry || savedConfig.npmRegistry
83 | ).replace(/\/$/, '');
84 | localStorage.setItem(LOCAL_CONFIG_KEY, JSON.stringify(savedConfig));
85 | };
86 |
87 | export const localConnect = () => {
88 | const localConfig = getLocalConfig();
89 | return fetch(`http://${localConfig.ip}:${localConfig.port}/api/connect`, {
90 | method: 'GET',
91 | headers: { 'content-type': 'application/json' },
92 | });
93 | };
94 |
95 | export const localSave = (data: any) => {
96 | const localConfig = getLocalConfig();
97 | fetch(`http://${localConfig.ip}:${localConfig.port}/api/save`, {
98 | method: 'POST',
99 | headers: { 'content-type': 'application/json' },
100 | body: JSON.stringify(data),
101 | });
102 | };
103 |
104 | export const queryGraph = (projectId: string) => {
105 | return request({
106 | url: '/api/queryGraph',
107 | params: {
108 | projectId,
109 | },
110 | });
111 | };
112 |
113 | export const modifyGraph = (
114 | projectId: string,
115 | actions: IModifyGraphAction[],
116 | ) => {
117 | return request({
118 | url: '/api/modifyGraph',
119 | params: {
120 | projectId,
121 | actions,
122 | },
123 | });
124 | };
125 |
--------------------------------------------------------------------------------
/packages/core/src/common/baseCell/behavior.ts:
--------------------------------------------------------------------------------
1 | import { Shape } from '@antv/x6';
2 |
3 | const schema = {
4 | base: Shape.Rect,
5 | shape: 'imove-behavior',
6 | width: 60,
7 | height: 30,
8 | label: '处理',
9 | attrs: {
10 | body: {
11 | fill: '#BCD0FF',
12 | stroke: '#6B8CD7',
13 | rx: 4,
14 | ry: 4,
15 | },
16 | label: {
17 | fill: '#333',
18 | fontSize: 13,
19 | fontWeight: 500,
20 | textWrap: { width: '100%' },
21 | },
22 | },
23 | ports: {
24 | groups: {
25 | top: {
26 | position: 'top',
27 | attrs: {
28 | circle: {
29 | r: 2.5,
30 | magnet: true,
31 | stroke: '#4E68A3',
32 | strokeWidth: 2,
33 | fill: '#fff',
34 | },
35 | },
36 | },
37 | right: {
38 | position: 'right',
39 | attrs: {
40 | circle: {
41 | r: 2.5,
42 | magnet: true,
43 | stroke: '#4E68A3',
44 | strokeWidth: 2,
45 | fill: '#fff',
46 | },
47 | },
48 | },
49 | bottom: {
50 | position: 'bottom',
51 | attrs: {
52 | circle: {
53 | r: 2.5,
54 | magnet: true,
55 | stroke: '#4E68A3',
56 | strokeWidth: 2,
57 | fill: '#fff',
58 | },
59 | },
60 | },
61 | left: {
62 | position: 'left',
63 | attrs: {
64 | circle: {
65 | r: 2.5,
66 | magnet: true,
67 | stroke: '#4E68A3',
68 | strokeWidth: 2,
69 | fill: '#fff',
70 | },
71 | },
72 | },
73 | },
74 | items: [
75 | {
76 | id: 'top',
77 | group: 'top',
78 | },
79 | {
80 | id: 'right',
81 | group: 'right',
82 | },
83 | {
84 | id: 'bottom',
85 | group: 'bottom',
86 | },
87 | {
88 | id: 'left',
89 | group: 'left',
90 | },
91 | ],
92 | },
93 | data: {
94 | label: '处理',
95 | configSchema: '{\n \n}',
96 | configData: {},
97 | dependencies: '{\n \n}',
98 | code: 'export default async function(ctx) {\n \n}',
99 | },
100 | };
101 |
102 | export default schema;
103 |
--------------------------------------------------------------------------------
/packages/core/src/common/baseCell/branch.ts:
--------------------------------------------------------------------------------
1 | import { Shape } from '@antv/x6';
2 |
3 | const schema = {
4 | base: Shape.Polygon,
5 | shape: 'imove-branch',
6 | width: 40,
7 | height: 30,
8 | attrs: {
9 | body: {
10 | strokeWidth: 2,
11 | stroke: '#FFB96B',
12 | fill: '#FFF6D1',
13 | refPoints: '0,10 10,0 20,10 10,20',
14 | },
15 | label: {
16 | refX: 0.5,
17 | refY: 1,
18 | text: '判断',
19 | fill: '#333',
20 | fontSize: 13,
21 | fontWeight: 500,
22 | textAnchor: 'middle',
23 | textVerticalAnchor: 'bottom',
24 | transform: 'matrix(1,0,0,1,0,-6)',
25 | },
26 | },
27 | ports: {
28 | groups: {
29 | top: {
30 | position: 'top',
31 | attrs: {
32 | circle: {
33 | r: 2.5,
34 | magnet: true,
35 | stroke: '#DD7500',
36 | strokeWidth: 2,
37 | fill: '#fff',
38 | },
39 | },
40 | },
41 | right: {
42 | position: 'right',
43 | attrs: {
44 | circle: {
45 | r: 2.5,
46 | magnet: true,
47 | stroke: '#DD7500',
48 | strokeWidth: 2,
49 | fill: '#fff',
50 | },
51 | },
52 | label: {
53 | position: 'right',
54 | },
55 | },
56 | bottom: {
57 | position: 'bottom',
58 | attrs: {
59 | circle: {
60 | r: 2.5,
61 | magnet: true,
62 | stroke: '#DD7500',
63 | strokeWidth: 2,
64 | fill: '#fff',
65 | },
66 | },
67 | label: {
68 | position: 'bottom',
69 | },
70 | },
71 | left: {
72 | position: 'left',
73 | attrs: {
74 | circle: {
75 | r: 2.5,
76 | magnet: true,
77 | stroke: '#DD7500',
78 | strokeWidth: 2,
79 | fill: '#fff',
80 | },
81 | },
82 | },
83 | },
84 | items: [
85 | {
86 | id: 'right',
87 | group: 'right',
88 | attrs: {
89 | text: {
90 | text: '',
91 | },
92 | },
93 | },
94 | {
95 | id: 'bottom',
96 | group: 'bottom',
97 | attrs: {
98 | text: {
99 | text: '',
100 | },
101 | },
102 | },
103 | {
104 | id: 'left',
105 | group: 'left',
106 | },
107 | ],
108 | },
109 | data: {
110 | label: '判断',
111 | configSchema: '{\n \n}',
112 | configData: {},
113 | dependencies: '{\n \n}',
114 | ports: {
115 | right: {
116 | condition: 'true',
117 | },
118 | bottom: {
119 | condition: 'false',
120 | },
121 | },
122 | code: 'export default async function(ctx) {\n return true;\n}',
123 | },
124 | };
125 |
126 | export default schema;
127 |
--------------------------------------------------------------------------------
/packages/core/src/common/baseCell/index.ts:
--------------------------------------------------------------------------------
1 | import start from './start';
2 | import branch from './branch';
3 | import behavior from './behavior';
4 |
5 | const cellSchemaMap: { [key: string]: any } = {
6 | 'imove-start': start,
7 | 'imove-branch': branch,
8 | 'imove-behavior': behavior,
9 | };
10 |
11 | export default cellSchemaMap;
12 |
--------------------------------------------------------------------------------
/packages/core/src/common/baseCell/start.ts:
--------------------------------------------------------------------------------
1 | import { Shape } from '@antv/x6';
2 |
3 | const schema = {
4 | base: Shape.Circle,
5 | shape: 'imove-start',
6 | width: 60,
7 | height: 60,
8 | label: '开始',
9 | attrs: {
10 | body: {
11 | fill: '#6B8CD7',
12 | strokeWidth: 2,
13 | stroke: '#4E68A3',
14 | },
15 | label: {
16 | fill: '#FFF',
17 | fontSize: 13,
18 | fontWeight: 500,
19 | textWrap: { width: '100%' },
20 | },
21 | },
22 | ports: {
23 | groups: {
24 | top: {
25 | position: 'top',
26 | zIndex: 10,
27 | attrs: {
28 | circle: {
29 | r: 2.5,
30 | magnet: true,
31 | stroke: '#4E68A3',
32 | strokeWidth: 2,
33 | fill: '#fff',
34 | },
35 | },
36 | },
37 | right: {
38 | position: 'right',
39 | zIndex: 10,
40 | attrs: {
41 | circle: {
42 | r: 2.5,
43 | magnet: true,
44 | stroke: '#4E68A3',
45 | strokeWidth: 2,
46 | fill: '#fff',
47 | },
48 | },
49 | },
50 | bottom: {
51 | position: 'bottom',
52 | zIndex: 10,
53 | attrs: {
54 | circle: {
55 | r: 2.5,
56 | magnet: true,
57 | stroke: '#4E68A3',
58 | strokeWidth: 2,
59 | fill: '#fff',
60 | },
61 | },
62 | },
63 | left: {
64 | position: 'left',
65 | zIndex: 10,
66 | attrs: {
67 | circle: {
68 | r: 2.5,
69 | magnet: true,
70 | stroke: '#4E68A3',
71 | strokeWidth: 2,
72 | fill: '#fff',
73 | },
74 | },
75 | },
76 | },
77 | items: [
78 | {
79 | id: 'top',
80 | group: 'top',
81 | },
82 | {
83 | id: 'right',
84 | group: 'right',
85 | },
86 | {
87 | id: 'bottom',
88 | group: 'bottom',
89 | },
90 | {
91 | id: 'left',
92 | group: 'left',
93 | },
94 | ],
95 | },
96 | data: {
97 | label: '开始',
98 | configSchema: '{\n \n}',
99 | configData: {},
100 | trigger: 'start',
101 | dependencies: '{\n \n}',
102 | code: 'export default async function(ctx) {\n \n}',
103 | },
104 | };
105 |
106 | export default schema;
107 |
--------------------------------------------------------------------------------
/packages/core/src/common/const.ts:
--------------------------------------------------------------------------------
1 | export const MIN_ZOOM = 0.5;
2 | export const MAX_ZOOM = 1.5;
3 | export const ZOOM_STEP = 0.1;
4 |
--------------------------------------------------------------------------------
/packages/core/src/common/previewCell/behavior.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | interface IProps {
6 | title?: string;
7 | }
8 |
9 | const Cell: React.FC = (props) => {
10 | const { title = '处理', ...rest } = props;
11 | return (
12 |
13 |
14 | {title}
15 |
16 |
17 | );
18 | };
19 |
20 | export default Cell;
21 |
--------------------------------------------------------------------------------
/packages/core/src/common/previewCell/branch.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | interface IProps {
6 | title?: string;
7 | }
8 |
9 | const Cell: React.FC = (props) => {
10 | const { title = '判断', ...rest } = props;
11 | const z = (n: any) => n * 1.2;
12 | return (
13 |
14 |
15 |
23 |
24 |
{title}
25 |
26 | );
27 | };
28 |
29 | export default Cell;
30 |
--------------------------------------------------------------------------------
/packages/core/src/common/previewCell/index.module.less:
--------------------------------------------------------------------------------
1 | .box {
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | width: 60px;
6 | height: 60px;
7 | background-color: #f2f2f2;
8 | position: relative;
9 | cursor: pointer;
10 | .text {
11 | z-index: 1;
12 | position: absolute;
13 | top: 15px;
14 | left: 0;
15 | width: 100%;
16 | height: 100%;
17 | font-size: 11px;
18 | height: 30px;
19 | line-height: 30px;
20 | text-align: center;
21 | font-weight: 500;
22 | }
23 | }
24 |
25 | .circle {
26 | display: flex;
27 | justify-content: center;
28 | align-items: center;
29 | width: 40px;
30 | height: 40px;
31 | border-radius: 30px;
32 | color: #FFF;
33 | background-color: #6B8CD7;
34 | border: 2px solid #4E68A3;
35 | }
36 |
37 | .rect {
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 | width: 52px;
42 | height: 30px;
43 | font-size: 12px;
44 | color: #333;
45 | border-radius: 4px;
46 | background-color: #BCD0FF;
47 | border: 2px solid #6B8CD7;
48 | }
--------------------------------------------------------------------------------
/packages/core/src/common/previewCell/index.ts:
--------------------------------------------------------------------------------
1 | import start from './start';
2 | import branch from './branch';
3 | import behavior from './behavior';
4 |
5 | const cellMap: { [key: string]: any } = {
6 | 'imove-start': start,
7 | 'imove-branch': branch,
8 | 'imove-behavior': behavior,
9 | };
10 |
11 | export default cellMap;
12 |
--------------------------------------------------------------------------------
/packages/core/src/common/previewCell/start.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | interface IProps extends React.HTMLProps {
6 | title?: string;
7 | }
8 |
9 | const Cell: React.FC = (props) => {
10 | const { title = '开始', ...rest } = props;
11 | return (
12 |
13 |
14 | {title}
15 |
16 |
17 | );
18 | };
19 |
20 | export default Cell;
21 |
--------------------------------------------------------------------------------
/packages/core/src/components/codeEditor/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useState, useEffect } from 'react';
2 |
3 | import {
4 | monaco,
5 | EditorDidMount,
6 | ControlledEditor,
7 | ControlledEditorProps,
8 | } from '@monaco-editor/react';
9 |
10 | import monokaiTheme from './theme-monokai';
11 |
12 | let KeyMod: any = {};
13 | let KeyCode: any = {};
14 | monaco.init().then((monaco) => {
15 | KeyMod = monaco.KeyMod;
16 | KeyCode = monaco.KeyCode;
17 | monaco.editor.defineTheme('monokai', monokaiTheme);
18 | });
19 |
20 | const CODE_EDITOR_OPTIONS = {
21 | fontSize: 14,
22 | };
23 |
24 | interface IProps extends ControlledEditorProps {
25 | onSave?: (code: string) => void;
26 | }
27 |
28 | export const CodeEditor: React.FC = (props) => {
29 | const { options, editorDidMount, onSave, ...rest } = props;
30 | const [editorInst, setEditorInst] = useState();
31 |
32 | const editorOptions = useMemo(() => {
33 | return Object.assign({}, CODE_EDITOR_OPTIONS, options);
34 | }, [options]);
35 |
36 | useEffect(() => {
37 | if (editorInst) {
38 | // NOTE: how to add command(https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.istandalonecodeeditor.html#addcommand)
39 | editorInst.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => {
40 | onSave && onSave(editorInst.getValue());
41 | });
42 | }
43 | }, [editorInst, onSave]);
44 |
45 | const onEditorDidMount: EditorDidMount = (getEditorValue, editor) => {
46 | setEditorInst(editor);
47 | editorDidMount && editorDidMount(getEditorValue, editor);
48 | };
49 |
50 | return (
51 |
58 | );
59 | };
60 |
61 | export default CodeEditor;
62 |
--------------------------------------------------------------------------------
/packages/core/src/components/codeEditor/theme-monokai.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | base: 'vs-dark',
3 | inherit: true,
4 | rules: [
5 | {
6 | background: '272822',
7 | token: '',
8 | },
9 | {
10 | foreground: '75715e',
11 | token: 'comment',
12 | },
13 | {
14 | foreground: 'e6db74',
15 | token: 'string',
16 | },
17 | {
18 | foreground: 'ae81ff',
19 | token: 'constant.numeric',
20 | },
21 | {
22 | foreground: 'ae81ff',
23 | token: 'constant.language',
24 | },
25 | {
26 | foreground: 'ae81ff',
27 | token: 'constant.character',
28 | },
29 | {
30 | foreground: 'ae81ff',
31 | token: 'constant.other',
32 | },
33 | {
34 | foreground: 'f92672',
35 | token: 'keyword',
36 | },
37 | {
38 | foreground: 'f92672',
39 | token: 'storage',
40 | },
41 | {
42 | foreground: '66d9ef',
43 | fontStyle: 'italic',
44 | token: 'storage.type',
45 | },
46 | {
47 | foreground: 'a6e22e',
48 | fontStyle: 'underline',
49 | token: 'entity.name.class',
50 | },
51 | {
52 | foreground: 'a6e22e',
53 | fontStyle: 'italic underline',
54 | token: 'entity.other.inherited-class',
55 | },
56 | {
57 | foreground: 'a6e22e',
58 | token: 'entity.name.function',
59 | },
60 | {
61 | foreground: 'fd971f',
62 | fontStyle: 'italic',
63 | token: 'variable.parameter',
64 | },
65 | {
66 | foreground: 'f92672',
67 | token: 'entity.name.tag',
68 | },
69 | {
70 | foreground: 'a6e22e',
71 | token: 'entity.other.attribute-name',
72 | },
73 | {
74 | foreground: '66d9ef',
75 | token: 'support.function',
76 | },
77 | {
78 | foreground: '66d9ef',
79 | token: 'support.constant',
80 | },
81 | {
82 | foreground: '66d9ef',
83 | fontStyle: 'italic',
84 | token: 'support.type',
85 | },
86 | {
87 | foreground: '66d9ef',
88 | fontStyle: 'italic',
89 | token: 'support.class',
90 | },
91 | {
92 | foreground: 'f8f8f0',
93 | background: 'f92672',
94 | token: 'invalid',
95 | },
96 | {
97 | foreground: 'f8f8f0',
98 | background: 'ae81ff',
99 | token: 'invalid.deprecated',
100 | },
101 | {
102 | foreground: 'cfcfc2',
103 | token: 'meta.structure.dictionary.json string.quoted.double.json',
104 | },
105 | {
106 | foreground: '75715e',
107 | token: 'meta.diff',
108 | },
109 | {
110 | foreground: '75715e',
111 | token: 'meta.diff.header',
112 | },
113 | {
114 | foreground: 'f92672',
115 | token: 'markup.deleted',
116 | },
117 | {
118 | foreground: 'a6e22e',
119 | token: 'markup.inserted',
120 | },
121 | {
122 | foreground: 'e6db74',
123 | token: 'markup.changed',
124 | },
125 | {
126 | foreground: 'ae81ffa0',
127 | token: 'constant.numeric.line-number.find-in-files - match',
128 | },
129 | {
130 | foreground: 'e6db74',
131 | token: 'entity.name.filename.find-in-files',
132 | },
133 | ],
134 | colors: {
135 | 'editor.foreground': '#F8F8F2',
136 | 'editor.background': '#272822',
137 | 'editor.selectionBackground': '#49483E',
138 | 'editor.lineHighlightBackground': '#3E3D32',
139 | 'editorCursor.foreground': '#F8F8F0',
140 | 'editorWhitespace.foreground': '#3B3A32',
141 | 'editorIndentGuide.activeBackground': '#9D550FB0',
142 | 'editor.selectionHighlightBorder': '#222218',
143 | },
144 | };
145 |
--------------------------------------------------------------------------------
/packages/core/src/components/codeRun/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: column;
4 | flex: 1;
5 | height: 100%;
6 |
7 | .pane {
8 | position: relative;
9 | display: flex;
10 | flex: 1;
11 |
12 | .runWrapper {
13 | position: absolute;
14 | top: 0;
15 | right: 0;
16 | }
17 | }
18 | }
19 |
20 | .card {
21 | display: flex;
22 | flex-direction: column;
23 | flex: 1;
24 | background-color: #ffffff;
25 |
26 | .cardTitleText {
27 | margin: 10px 24px 0;
28 | font-size: 16px;
29 | font-weight: 500;
30 | color: #000000;
31 | }
32 |
33 | .cardBody {
34 | padding: 10px 24px;
35 | height: 100%;
36 | overflow: scroll;
37 | }
38 | }
39 |
40 | :global {
41 | .ant-tabs {
42 | height: 100%;
43 | }
44 |
45 | .ant-tabs-content-holder {
46 | height: 100%;
47 | }
48 |
49 | .ant-tabs-content {
50 | height: 100%;
51 | overflow: scroll;
52 | }
53 |
54 | .sc-bdVaJa.cjjWdp {
55 | opacity: 1;
56 | background-color: #efefef;
57 |
58 | &:hover {
59 | border-color: transparent;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/core/src/components/codeRun/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Button } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import Console from '../console';
8 | import InputPanel from './inputPanel';
9 | import JsonView from 'react-json-view';
10 | import { executeScript } from '../../utils';
11 | import { PlayCircleFilled, LoadingOutlined } from '@ant-design/icons';
12 | import { compileForOnline } from '@imove/compile-code';
13 | import { toSelectedCellsJSON } from '../../utils/flowChartUtils';
14 | import SplitPane from 'react-split-pane-v2';
15 |
16 | const Pane = SplitPane.Pane;
17 |
18 | const defaultInput = {
19 | pipe: {},
20 | context: {},
21 | payload: {},
22 | config: {},
23 | };
24 |
25 | interface ICardProps {
26 | title: string;
27 | }
28 |
29 | const Card: React.FC = (props) => {
30 | const { title } = props;
31 |
32 | return (
33 |
34 |
{title}
35 |
{props.children}
36 |
37 | );
38 | };
39 |
40 | interface ICodeRunProps {
41 | flowChart: Graph;
42 | }
43 |
44 | function isJson(obj: any) {
45 | var t = typeof obj;
46 | return ['boolean', 'number', 'string', 'symbol', 'function'].indexOf(t) == -1;
47 | }
48 |
49 | const CodeRun: React.FC = (props) => {
50 | const { flowChart } = props;
51 | const [isRunning, setIsRunning] = useState(false);
52 | const [input, setInput] = useState(defaultInput);
53 | const [output, setOutput] = useState({});
54 |
55 | useEffect(() => {
56 | // NOTE: listen the event that iMove online exec ends
57 | const handler = (data: any) => {
58 | setIsRunning(false);
59 | // console.dir(data)
60 |
61 | if (isJson(data.detail) == true) {
62 | setOutput(data.detail || {});
63 | // console.dir( data.detail)
64 | }
65 | };
66 | window.addEventListener('iMoveOnlineExecEnds', handler);
67 | return () => {
68 | window.removeEventListener('iMoveOnlineExecEnds', handler);
69 | };
70 | }, []);
71 |
72 | const onClickRun = useCallback(() => {
73 | setIsRunning(true);
74 | const selectedCelssJson = toSelectedCellsJSON(flowChart);
75 | const compiledCode = compileForOnline(selectedCelssJson, input);
76 | executeScript(compiledCode);
77 | }, [flowChart, input]);
78 |
79 | const onChangeInput = useCallback((val: any) => {
80 | setInput(val);
81 | }, []);
82 |
83 | return (
84 |
85 |
86 |
87 |
88 |
89 |
90 | {isRunning ? (
91 |
92 | 运行中...
93 |
94 | ) : (
95 |
96 | 运行代码
97 |
98 | )}
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | );
124 | };
125 |
126 | export default CodeRun;
127 |
--------------------------------------------------------------------------------
/packages/core/src/components/codeRun/inputPanel/index.module.less:
--------------------------------------------------------------------------------
1 | .itemHeader {
2 | margin-bottom: 8px;
3 |
4 | .itemTitleText {
5 | font-size: 15px;
6 | font-weight: 500;
7 | }
8 |
9 | .itemDescText {
10 | margin-left: 5px;
11 | font-size: 12px;
12 | color: #999;
13 | }
14 | }
15 |
16 | :global {
17 | .ant-tabs-nav {
18 | margin-bottom: 5px;
19 | }
20 |
21 | .ant-form-item {
22 | margin-bottom: 8px;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/components/colorPicker/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import { SketchPicker, ColorResult } from 'react-color';
4 |
5 | interface IProps {
6 | color: string;
7 | onChangeComplete: (color: string) => void;
8 | }
9 |
10 | const ColorPicker: React.FC = (props) => {
11 | const { color, onChangeComplete: changeCb } = props;
12 | const [curColor, setCurColor] = useState(color);
13 |
14 | // life
15 | useEffect(() => setCurColor(color), [color]);
16 |
17 | // events
18 | const onChange = (color: ColorResult): void => setCurColor(color.hex);
19 | const onChangeComplete = (color: ColorResult): void => changeCb(color.hex);
20 |
21 | return (
22 |
27 | );
28 | };
29 |
30 | export default ColorPicker;
31 |
--------------------------------------------------------------------------------
/packages/core/src/components/console/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 | height: 100%;
4 | overflow: scroll;
5 |
6 | :global {
7 | .ant-tabs-nav {
8 | margin-bottom: 0;
9 | }
10 | }
11 |
12 | .logPanel {
13 | height: 100%;
14 | background: #272823;
15 |
16 | .logLine {
17 | display: flex;
18 | flex-direction: row;
19 | position: relative;
20 | margin-top: -1px;
21 | padding-left: 10px;
22 | font-size: 13px;
23 | border: 1px solid transparent;
24 |
25 | &:first-child {
26 | margin-top: 0;
27 | }
28 |
29 | .logIcon {
30 | padding-top: 0.6rem;
31 | width: 1rem;
32 | height: 1rem;
33 | }
34 |
35 | .logContent {
36 | margin-left: 8px;
37 | min-height: 18px;
38 | padding: 0.4rem 1.5rem 0.4rem 0px;
39 | word-break: break-all;
40 |
41 | :global {
42 | .react-json-view {
43 | display: inline-block;
44 | vertical-align: top;
45 | }
46 | }
47 | }
48 | }
49 | }
50 |
51 | .toolBar {
52 | display: flex;
53 | flex-direction: row;
54 |
55 | .levels {
56 | margin-left: 4px;
57 | width: 150px;
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/packages/core/src/components/miniMapSimpleNode/index.tsx:
--------------------------------------------------------------------------------
1 | import { NodeView } from '@antv/x6';
2 |
3 | class MiniMapSimpleNode extends NodeView {
4 | protected renderMarkup() {
5 | return this.renderJSONMarkup({
6 | tagName: 'rect',
7 | selector: 'body',
8 | });
9 | }
10 |
11 | protected renderPorts() {
12 | return null;
13 | }
14 |
15 | update() {
16 | super.update({
17 | body: {
18 | refWidth: '100%',
19 | refHeight: '100%',
20 | fill: '#D9D9D9',
21 | },
22 | });
23 | }
24 | }
25 |
26 | export default MiniMapSimpleNode;
27 |
--------------------------------------------------------------------------------
/packages/core/src/components/schemaForm/index.module.less:
--------------------------------------------------------------------------------
1 | .btnWrap{
2 | width:100%;
3 | display:flex;
4 | flex-direction: row;
5 | justify-content:flex-end;
6 | .btn{
7 | margin-top: 20px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/components/xIcon/index.less:
--------------------------------------------------------------------------------
1 | #icon-select-all,
2 | #icon-align-top,
3 | #icon-align-left,
4 | #icon-align-vertical-center,
5 | #icon-align-horizontal-center,
6 | #icon-align-bottom,
7 | #icon-align-right {
8 | path {
9 | fill: currentColor;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/components/xIcon/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createFromIconfontCN } from '@ant-design/icons';
3 | import './index.less';
4 |
5 | interface IconFontProps {
6 | type: string;
7 | className?: string;
8 | }
9 |
10 | const XIcon: React.SFC = createFromIconfontCN({
11 | scriptUrl: '//at.alicdn.com/t/font_2024452_r7nz0cw949.js',
12 | });
13 |
14 | export default XIcon;
15 |
--------------------------------------------------------------------------------
/packages/core/src/global.index.less:
--------------------------------------------------------------------------------
1 | .react-json-view {
2 | .collapsed-icon, .expanded-icon {
3 | svg {
4 | vertical-align: middle !important;
5 | }
6 | }
7 | }
8 |
9 | .x6-node {
10 |
11 | .x6-port-body {
12 | visibility: hidden;
13 | }
14 |
15 | &:hover {
16 | .x6-port-body {
17 | visibility: visible;
18 | }
19 | }
20 | }
--------------------------------------------------------------------------------
/packages/core/src/hooks/useClickAway.ts:
--------------------------------------------------------------------------------
1 | import { useRef, useEffect, MutableRefObject } from 'react';
2 |
3 | type EventType = MouseEvent | TouchEvent;
4 | type TargetElement = HTMLElement | Element | Document | Window;
5 | type BasicTarget =
6 | | (() => T | null)
7 | | T
8 | | null
9 | | MutableRefObject;
10 |
11 | const defaultEvent = 'click';
12 |
13 | const getTargetElement = (
14 | target: BasicTarget,
15 | defaultElement?: TargetElement,
16 | ): TargetElement | undefined | null => {
17 | if (!target) {
18 | return defaultElement;
19 | }
20 |
21 | let targetElement: TargetElement | undefined | null;
22 | if (typeof target === 'function') {
23 | targetElement = target();
24 | } else if ('current' in target) {
25 | targetElement = target.current;
26 | } else {
27 | targetElement = target;
28 | }
29 |
30 | return targetElement;
31 | };
32 |
33 | const useClickAway = (
34 | onClickAway: (event: EventType) => void,
35 | target: BasicTarget | BasicTarget[],
36 | eventName: string = defaultEvent,
37 | ): void => {
38 | const onClickAwayRef = useRef(onClickAway);
39 | onClickAwayRef.current = onClickAway;
40 | useEffect(() => {
41 | const handler = (event: any) => {
42 | const targets = Array.isArray(target) ? target : [target];
43 | if (
44 | targets.some((targetItem) => {
45 | const targetElement = getTargetElement(targetItem) as HTMLElement;
46 | return !targetElement || targetElement?.contains(event.target);
47 | })
48 | ) {
49 | return;
50 | }
51 | onClickAwayRef.current(event);
52 | };
53 | document.addEventListener(eventName, handler);
54 |
55 | return () => {
56 | document.removeEventListener(eventName, handler);
57 | };
58 | }, [target, eventName]);
59 | };
60 |
61 | export default useClickAway;
62 |
--------------------------------------------------------------------------------
/packages/core/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import './global.index.less';
4 | import '@antv/x6/dist/x6.css';
5 |
6 | import { Graph } from '@antv/x6';
7 | import Layout from './mods/layout';
8 | import Header from './mods/header';
9 | import SideBar from './mods/sideBar';
10 | import ToolBar from './mods/toolBar';
11 | import FlowChart from './mods/flowChart';
12 | import SettingBar from './mods/settingBar';
13 |
14 | interface IProps {
15 | onSave: (data: { nodes: any; edges: any }) => void;
16 | }
17 |
18 | const Core: React.FC = (props) => {
19 | const { onSave } = props;
20 | const [flowChart, setFlowChart] = useState();
21 | const onFlowChartReady = (flowChart: Graph): void => setFlowChart(flowChart);
22 | return (
23 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default Core;
36 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/codeEditorModal/index.module.less:
--------------------------------------------------------------------------------
1 | .depsInfoModalContent {
2 | padding-top: 20px;
3 | }
4 |
5 | .modal {
6 | :global {
7 | .ant-modal-body {
8 | padding: 0;
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/codeEditorModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import 'antd/es/modal/style';
4 | import styles from './index.module.less';
5 |
6 | import { Graph } from '@antv/x6';
7 | import { Button, Modal, message } from 'antd';
8 | import JsonView from 'react-json-view';
9 | import { safeParse } from '../../../utils';
10 | import analyzeDeps from '../../../utils/analyzeDeps';
11 | import CodeEditor from '../../../components/codeEditor';
12 |
13 | interface IProps {
14 | title?: string;
15 | flowChart: Graph;
16 | }
17 |
18 | const CodeEditModal: React.FC = (props) => {
19 | const { title = '编辑代码', flowChart } = props;
20 | const [code, setCode] = useState('');
21 | const [visible, setVisible] = useState(false);
22 |
23 | const updateNodeCode = (code: string): void => {
24 | const cell = flowChart.getSelectedCells()[0];
25 | const { code: oldCode, dependencies } = cell.getData();
26 | if (code === oldCode) {
27 | return;
28 | }
29 |
30 | cell.setData({ code });
31 | message.success('代码保存成功', 1);
32 | const excludeDeps = safeParse(dependencies);
33 | analyzeDeps(code, Object.keys(excludeDeps)).then((deps): void => {
34 | if (Object.keys(deps).length > 0) {
35 | Modal.info({
36 | title: '检测到您的代码有新依赖,已为您自动更新',
37 | content: (
38 |
39 |
47 |
48 | ),
49 | onOk() {
50 | const newDeps = { ...excludeDeps, ...deps };
51 | cell.setData({
52 | code,
53 | dependencies: JSON.stringify(newDeps, null, 2),
54 | });
55 | // NOTE: notify basic panel to update dependency
56 | flowChart.trigger('settingBar.basicPanel:forceUpdate');
57 | },
58 | });
59 | }
60 | });
61 | };
62 |
63 | // life
64 | useEffect(() => {
65 | const handler = () => setVisible(true);
66 | flowChart.on('graph:editCode', handler);
67 | return () => {
68 | flowChart.off('graph:editCode', handler);
69 | };
70 | }, []);
71 | useEffect(() => {
72 | if (visible) {
73 | const cell = flowChart.getSelectedCells()[0];
74 | const { code } = cell.getData() || {};
75 | setCode(code);
76 | } else {
77 | setCode('');
78 | }
79 | }, [visible]);
80 |
81 | // events
82 | const onOk = (): void => {
83 | setVisible(false);
84 | updateNodeCode(code);
85 | };
86 | const onCancel = (): void => {
87 | setVisible(false);
88 | };
89 | const onRunCode = (): void => {
90 | updateNodeCode(code);
91 | flowChart.trigger('graph:runCode');
92 | };
93 | const onChangeCode = (ev: any, newCode: string | undefined = ''): void => {
94 | setCode(newCode);
95 | };
96 | const onSaveCode = (newCode: string) => {
97 | updateNodeCode(newCode);
98 | };
99 |
100 | return (
101 |
109 | 取消
110 | ,
111 |
112 | 运行代码
113 | ,
114 |
115 | 保存
116 | ,
117 | ]}
118 | >
119 |
126 |
127 | );
128 | };
129 |
130 | export default CodeEditModal;
131 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/codeRunModal/index.module.less:
--------------------------------------------------------------------------------
1 | .modal {
2 | :global {
3 | .ant-modal-body {
4 | padding: 0;
5 | height: 653px;
6 | }
7 | }
8 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/codeRunModal/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Modal } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import CodeRun from '../../../components/codeRun';
8 |
9 | interface IEditModalProps {
10 | title?: string;
11 | flowChart: Graph;
12 | }
13 |
14 | const CodeRunModal: React.FC = (props): JSX.Element => {
15 | const { title = '执行代码', flowChart } = props;
16 | const [visible, setVisible] = useState(false);
17 |
18 | useEffect(() => {
19 | const handler = () => setVisible(true);
20 | flowChart.on('graph:runCode', handler);
21 | return () => {
22 | flowChart.off('graph:runCode', handler);
23 | };
24 | }, [flowChart]);
25 |
26 | // events
27 | const onClose = useCallback((): void => {
28 | setVisible(false);
29 | }, []);
30 |
31 | return (
32 |
40 |
41 |
42 | );
43 | };
44 |
45 | export default CodeRunModal;
46 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/contextMenu/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useCallback } from 'react';
2 |
3 | import styles from '../index.module.less';
4 |
5 | import { Menu } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import useClickAway from '../../../hooks/useClickAway';
8 | import { nodeMenuConfig, blankMenuConfig } from './menuConfig';
9 |
10 | interface IProps {
11 | x: number;
12 | y: number;
13 | scene: string;
14 | visible: boolean;
15 | flowChart: Graph;
16 | }
17 |
18 | interface IMenuConfig {
19 | key: string;
20 | title: string;
21 | icon?: React.ReactElement;
22 | children?: IMenuConfig[];
23 | showDividerBehind?: boolean;
24 | disabled?: boolean | ((flowChart: Graph) => boolean);
25 | handler: (flowChart: Graph) => void;
26 | }
27 |
28 | const menuConfigMap: { [scene: string]: IMenuConfig[] } = {
29 | node: nodeMenuConfig,
30 | blank: blankMenuConfig,
31 | };
32 |
33 | const FlowChartContextMenu: React.FC = (props) => {
34 | const menuRef = useRef(null);
35 | const { x, y, scene, visible, flowChart } = props;
36 | const menuConfig = menuConfigMap[scene];
37 |
38 | useClickAway(() => onClickAway(), menuRef);
39 |
40 | const onClickAway = useCallback(
41 | () => flowChart.trigger('graph:hideContextMenu'),
42 | [flowChart],
43 | );
44 | const onClickMenu = useCallback(
45 | ({ key }) => {
46 | const handlerMap = Helper.makeMenuHandlerMap(menuConfig);
47 | const handler = handlerMap[key];
48 | if (handler) {
49 | onClickAway();
50 | handler(flowChart);
51 | }
52 | },
53 | [flowChart, menuConfig],
54 | );
55 |
56 | return !visible ? null : (
57 |
62 |
63 | {Helper.makeMenuContent(flowChart, menuConfig)}
64 |
65 |
66 | );
67 | };
68 |
69 | const Helper = {
70 | makeMenuHandlerMap(config: IMenuConfig[]) {
71 | const queue = config.slice(0);
72 | const handlerMap: { [key: string]: (flowChart: Graph) => void } = {};
73 | while (queue.length > 0) {
74 | const { key, handler, children } = queue.pop() as IMenuConfig;
75 | if (children && children.length > 0) {
76 | queue.push(...children);
77 | } else {
78 | handlerMap[key] = handler;
79 | }
80 | }
81 | return handlerMap;
82 | },
83 | makeMenuContent(flowChart: Graph, menuConfig: IMenuConfig[]) {
84 | const loop = (config: IMenuConfig[]) => {
85 | return config.map((item) => {
86 | let content = null;
87 | let {
88 | key,
89 | title,
90 | icon,
91 | children,
92 | disabled = false,
93 | showDividerBehind,
94 | } = item;
95 | if (typeof disabled === 'function') {
96 | disabled = disabled(flowChart);
97 | }
98 | if (children && children.length > 0) {
99 | content = (
100 |
106 | {loop(children)}
107 |
108 | );
109 | } else {
110 | content = (
111 |
112 | {title}
113 |
114 | );
115 | }
116 | return [content, showDividerBehind && ];
117 | });
118 | };
119 | return loop(menuConfig);
120 | },
121 | };
122 |
123 | export default FlowChartContextMenu;
124 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/contextMenu/menuConfig/blank.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import XIcon from '../../../../components/xIcon';
5 | import shortcuts from '../../../../common/shortcuts';
6 | import { SnippetsOutlined } from '@ant-design/icons';
7 |
8 | const blankMenuConfig = [
9 | {
10 | key: 'selectAll',
11 | title: '全选',
12 | icon: ,
13 | handler: shortcuts.selectAll.handler,
14 | },
15 | {
16 | key: 'paste',
17 | title: '粘贴',
18 | icon: ,
19 | disabled: (flowChart: Graph) => flowChart.isClipboardEmpty(),
20 | handler: shortcuts.paste.handler,
21 | },
22 | ];
23 |
24 | export default blankMenuConfig;
25 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/contextMenu/menuConfig/index.ts:
--------------------------------------------------------------------------------
1 | import nodeMenuConfig from './node';
2 | import blankMenuConfig from './blank';
3 |
4 | export { nodeMenuConfig, blankMenuConfig };
5 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/contextMenu/menuConfig/node.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import {
4 | CopyOutlined,
5 | EditOutlined,
6 | CodeOutlined,
7 | FormOutlined,
8 | DeleteOutlined,
9 | } from '@ant-design/icons';
10 |
11 | import { Graph } from '@antv/x6';
12 | import XIcon from '../../../../components/xIcon';
13 | import shortcuts from '../../../../common/shortcuts';
14 | import { getSelectedNodes } from '../../../../utils/flowChartUtils';
15 |
16 | const nodeMenuConfig = [
17 | {
18 | key: 'copy',
19 | title: '复制',
20 | icon: ,
21 | handler: shortcuts.copy.handler,
22 | },
23 | {
24 | key: 'delete',
25 | title: '删除',
26 | icon: ,
27 | handler: shortcuts.delete.handler,
28 | },
29 | {
30 | key: 'rename',
31 | title: '编辑文本',
32 | icon: ,
33 | showDividerBehind: true,
34 | handler() {
35 | // TODO
36 | },
37 | },
38 | {
39 | key: 'bringToTop',
40 | title: '置于顶层',
41 | icon: ,
42 | handler: shortcuts.bringToTop.handler,
43 | },
44 | {
45 | key: 'bringToBack',
46 | title: '置于底层',
47 | icon: ,
48 | showDividerBehind: true,
49 | handler: shortcuts.bringToBack.handler,
50 | },
51 | {
52 | key: 'editCode',
53 | title: '编辑代码',
54 | icon: ,
55 | disabled(flowChart: Graph) {
56 | return getSelectedNodes(flowChart).length !== 1;
57 | },
58 | handler(flowChart: Graph) {
59 | flowChart.trigger('graph:editCode');
60 | },
61 | },
62 | {
63 | key: 'executeCode',
64 | title: '执行代码',
65 | icon: ,
66 | disabled(flowChart: Graph) {
67 | return getSelectedNodes(flowChart).length !== 1;
68 | },
69 | handler(flowChart: Graph) {
70 | flowChart.trigger('graph:runCode');
71 | },
72 | },
73 | ];
74 |
75 | export default nodeMenuConfig;
76 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 |
3 | position: relative;
4 | display: flex;
5 | flex: 1;
6 | width: 100%;
7 | height: 100%;
8 |
9 | .flowChart {
10 | width: 100%;
11 | height: 100%;
12 | }
13 |
14 | .miniMap {
15 | position: absolute;
16 | right: 40px;
17 | bottom: 40px;
18 |
19 | :global {
20 | .x6-widget-minimap-viewport {
21 | border: 1px solid #E0E0E0;
22 | }
23 | }
24 | }
25 |
26 | .contextMenu {
27 | z-index: 1003;
28 | position: fixed;
29 | min-width: 200px;
30 | box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05) !important;
31 | }
32 | }
33 |
34 | // add global for x6
35 | :global {
36 | .x6-widget-transform{
37 | border-color:rgba(0, 0, 0, 0.3);
38 | border-style: dashed;
39 | background-color: rgba(0, 0, 0, 0.14);
40 | border-radius: 0;
41 | padding: 6px;
42 | margin: -7px 0 0 -7px;
43 | .x6-widget-transform-resize{
44 | width: 6px;
45 | height: 6px;
46 | border-radius: 0;
47 | border-color:rgba(0, 0, 0, 0.3);
48 | // border: 0 none;
49 | box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.15);
50 | }
51 | .x6-widget-transform-cursor-nw{
52 | top: -6px;
53 | left: -6px;
54 | }
55 | .x6-widget-transform-cursor-n{
56 | margin-left: -3px;
57 | top: -6px;
58 | }
59 | .x6-widget-transform-cursor-ne{
60 | top: -6px;
61 | right: -6px;
62 | }
63 | .x6-widget-transform-cursor-e{
64 | margin-top: -3px;
65 | right: -6px;
66 | }
67 | .x6-widget-transform-cursor-se{
68 | right: -6px;
69 | bottom: -6px;
70 | }
71 | .x6-widget-transform-cursor-s{
72 | margin-left: -3px;
73 | bottom: -6px;
74 | }
75 | .x6-widget-transform-cursor-sw{
76 | left: -6px;
77 | bottom: -6px;
78 | }
79 | .x6-widget-transform-cursor-w{
80 | left: -6px;
81 | margin-top: -3px;
82 | }
83 | }
84 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useEffect } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 | import { queryGraph } from '../../api';
7 | import { parseQuery } from '../../utils';
8 | import createFlowChart from './createFlowChart';
9 | import CodeRunModal from './codeRunModal';
10 | import CodeEditorModal from './codeEditorModal';
11 | import FlowChartContextMenu from './contextMenu';
12 |
13 | interface IProps {
14 | onReady: (graph: Graph) => void;
15 | }
16 |
17 | interface IMenuInfo {
18 | x: number;
19 | y: number;
20 | scene: string;
21 | visible: boolean;
22 | }
23 |
24 | const defaultMenuInfo = {
25 | x: 0,
26 | y: 0,
27 | scene: 'blank',
28 | visible: false,
29 | };
30 |
31 | const FlowChart: React.FC = (props) => {
32 | const { onReady } = props;
33 | const wrapperRef = useRef(null);
34 | const graphRef = useRef(null);
35 | const miniMapRef = useRef(null);
36 | const [flowChart, setFlowChart] = useState();
37 | const [contextMenuInfo, setContextMenuInfo] = useState(
38 | defaultMenuInfo,
39 | );
40 |
41 | useEffect(() => {
42 | if (graphRef.current && miniMapRef.current) {
43 | const flowChart = createFlowChart(graphRef.current, miniMapRef.current);
44 | onReady(flowChart);
45 | fetchData(flowChart);
46 | setFlowChart(flowChart);
47 | }
48 | }, []);
49 |
50 | // resize flowChart's size when window size changes
51 | useEffect(() => {
52 | const handler = () => {
53 | requestAnimationFrame(() => {
54 | if (flowChart && wrapperRef && wrapperRef.current) {
55 | const width = wrapperRef.current.clientWidth;
56 | const height = wrapperRef.current.clientHeight;
57 | flowChart.resize(width, height);
58 | }
59 | });
60 | };
61 | window.addEventListener('resize', handler);
62 | return () => {
63 | window.removeEventListener('resize', handler);
64 | };
65 | }, [flowChart, wrapperRef]);
66 |
67 | // NOTE: listen toggling context menu event
68 | useEffect(() => {
69 | const showHandler = (info: IMenuInfo) => {
70 | flowChart?.lockScroller();
71 | setContextMenuInfo({ ...info, visible: true });
72 | };
73 | const hideHandler = () => {
74 | flowChart?.unlockScroller();
75 | setContextMenuInfo({ ...contextMenuInfo, visible: false });
76 | };
77 | if (flowChart) {
78 | flowChart.on('graph:showContextMenu', showHandler);
79 | flowChart.on('graph:hideContextMenu', hideHandler);
80 | }
81 | return () => {
82 | if (flowChart) {
83 | flowChart.off('graph:showContextMenu', showHandler);
84 | flowChart.off('graph:hideContextMenu', hideHandler);
85 | }
86 | };
87 | }, [flowChart]);
88 |
89 | const fetchData = (flowChart: Graph) => {
90 | const { projectId } = parseQuery();
91 | queryGraph(projectId as string)
92 | .then((res) => {
93 | const { data: dsl } = res;
94 | flowChart.fromJSON(dsl);
95 | })
96 | .catch((error) => {
97 | console.log('query graph data failed, the error is:', error);
98 | });
99 | };
100 |
101 | return (
102 |
103 |
104 |
105 | {flowChart &&
}
106 | {flowChart &&
}
107 | {flowChart && (
108 |
109 | )}
110 |
111 | );
112 | };
113 |
114 | export default FlowChart;
115 |
--------------------------------------------------------------------------------
/packages/core/src/mods/flowChart/registerServerStorage.ts:
--------------------------------------------------------------------------------
1 | import { Graph } from '@antv/x6';
2 | import merge from 'lodash.merge';
3 | import { parseQuery } from '../../utils';
4 | import { modifyGraph, ActionType, IModifyGraphAction } from '../../api';
5 |
6 | const { projectId } = parseQuery();
7 | const memQueue: IModifyGraphAction[] = [];
8 |
9 | const validate = (type: string, data: any) => {
10 | if (type === 'node') {
11 | return true;
12 | } else if (type === 'edge') {
13 | const { source, target } = data;
14 | return source.cell && target.cell;
15 | } else {
16 | return false;
17 | }
18 | };
19 |
20 | const enqueue = (cellType: string, actionType: ActionType, data: any) => {
21 | if (!validate(cellType, data)) {
22 | return;
23 | }
24 |
25 | const foundIndex = memQueue.findIndex(
26 | (item) =>
27 | item.type === cellType &&
28 | item.actionType === actionType &&
29 | item.data.id === data.id,
30 | );
31 | if (foundIndex > -1) {
32 | const deleted = memQueue.splice(foundIndex, 1)[0];
33 | merge(deleted.data, data);
34 | }
35 | memQueue.push({ type: cellType, actionType, data });
36 | };
37 |
38 | let modifyActionTimer = -1;
39 | const save = (
40 | flowChart: Graph,
41 | cellType: string,
42 | actionType: ActionType,
43 | data: any,
44 | ) => {
45 | enqueue(cellType, actionType, data);
46 | clearTimeout(modifyActionTimer);
47 | modifyActionTimer = window.setTimeout(() => {
48 | const pushedActions = memQueue.slice(0);
49 | if (pushedActions.length > 0) {
50 | flowChart.trigger('graph:change:modify');
51 | modifyGraph(projectId, memQueue)
52 | .then((res) => {
53 | memQueue.splice(0, pushedActions.length);
54 | flowChart.trigger('graph:modified', { success: true });
55 | })
56 | .catch((error) => {
57 | flowChart.trigger('graph:modified', { success: true, error: error });
58 | });
59 | }
60 | }, 100);
61 | };
62 |
63 | type ActionEventMap = { [key: string]: string[] };
64 | const nodeActionEventMap: ActionEventMap = {
65 | [ActionType.create]: ['node:added'],
66 | [ActionType.remove]: ['node:removed'],
67 | [ActionType.update]: [
68 | 'node:moved',
69 | 'node:resized',
70 | 'node:rotated',
71 | 'node:change:ports',
72 | 'node:change:attrs',
73 | 'node:change:data',
74 | 'node:change:zIndex',
75 | ],
76 | };
77 |
78 | const edgeActionEventMap: ActionEventMap = {
79 | [ActionType.create]: ['edge:connected'],
80 | [ActionType.remove]: ['edge:removed'],
81 | [ActionType.update]: ['edge:moved'],
82 | };
83 |
84 | export const registerServerStorage = (flowChart: Graph) => {
85 | Object.keys(nodeActionEventMap).forEach((actionType) => {
86 | const events = nodeActionEventMap[actionType];
87 | events.forEach((event) => {
88 | flowChart.on(event, (args: any) => {
89 | save(flowChart, 'node', actionType as ActionType, args.node.toJSON());
90 | });
91 | });
92 | });
93 |
94 | Object.keys(edgeActionEventMap).forEach((actionType) => {
95 | const events = edgeActionEventMap[actionType];
96 | events.forEach((event) => {
97 | flowChart.on(event, (args: any) => {
98 | console.log('edge event:', event, 'args:', args);
99 | save(flowChart, 'edge', actionType as ActionType, args.edge.toJSON());
100 | });
101 | });
102 | });
103 | };
104 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/configuration/editModal.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import 'antd/es/form/style';
3 | import 'antd/es/modal/style';
4 | import 'antd/es/input/style';
5 | import styles from './index.module.less';
6 |
7 | import { Input, Form, Modal } from 'antd';
8 | import { FormInstance } from 'antd/lib/form';
9 | import { ILocalConfig, getLocalConfig, updateLocalConfig } from '../../../api';
10 |
11 | const PORT_REGEX = /^\d{1,5}$/;
12 | const IP_REGEX = /^(localhost)|(((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[01]?\d{1,2})){3})$/;
13 | const NPM_REGISTRY_REGEX = /^https?:\/\//; // 简单 URL 检查
14 |
15 | const makeRules = (regex: RegExp, errTip: string) => {
16 | return [
17 | { required: true, message: '不能为空!' },
18 | {
19 | validator: (_: any, val: string) =>
20 | regex.test(val) ? Promise.resolve() : Promise.reject(errTip),
21 | },
22 | ];
23 | };
24 |
25 | interface IEditorModalProps {
26 | visible: boolean;
27 | onOk: () => void;
28 | onCancel: () => void;
29 | }
30 |
31 | const EditModal: React.FC = (props: IEditorModalProps) => {
32 | const { visible, onOk, onCancel } = props;
33 | const formRef = useRef(null);
34 |
35 | // events
36 | const onClickOk = (): void => {
37 | if (formRef.current) {
38 | formRef.current
39 | .validateFields()
40 | .then((formValues) => {
41 | updateLocalConfig(formValues as ILocalConfig);
42 | onOk();
43 | })
44 | .catch((error) => {
45 | console.log(error.message);
46 | });
47 | }
48 | };
49 |
50 | return (
51 |
60 |
71 |
72 |
73 |
78 |
79 |
80 |
85 |
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | export default EditModal;
93 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/configuration/index.module.less:
--------------------------------------------------------------------------------
1 | .editModal {
2 |
3 | width: 450px;
4 |
5 | :global {
6 | .ant-form-item:last-child {
7 | margin-bottom: 0;
8 | }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/configuration/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import { Button } from 'antd';
3 | import { SettingOutlined } from '@ant-design/icons';
4 | import EditModal from './editModal';
5 |
6 | interface IProps {
7 | confirmToSync: () => Promise;
8 | }
9 |
10 | const Configuration: React.FC = (props: IProps) => {
11 | const [visible, setVisible] = useState(false);
12 |
13 | // events
14 | const onOpenEditModal = useCallback(() => {
15 | setVisible(true);
16 | }, [setVisible]);
17 | const onCloseEditModal = useCallback(() => {
18 | setVisible(false);
19 | }, []);
20 | const onOk = useCallback(() => {
21 | onCloseEditModal();
22 | props.confirmToSync();
23 | }, [onCloseEditModal]);
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default Configuration;
36 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/connectStatus/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | display: flex;
3 | flex-direction: row;
4 | align-items: center;
5 | margin-left: 20px;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/connectStatus/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import 'antd/es/tag/style';
4 | import { Tag } from 'antd';
5 | import styles from './index.module.less';
6 |
7 | export enum Status {
8 | connected = 'success',
9 | disconnected = 'error',
10 | }
11 |
12 | interface IProps {
13 | // flowChart: Graph;
14 | status: Status;
15 | projectName: string;
16 | syncLocal: () => Promise;
17 | confirmToSync: () => Promise;
18 | }
19 |
20 | const PULSE_RATE = 10 * 1000; // try to connect to local servet per 10 seconds
21 |
22 | const ConnectStatus: React.FC = (props: IProps) => {
23 | const { status, confirmToSync, syncLocal, projectName } = props;
24 |
25 | // life
26 | useEffect(() => {
27 | confirmToSync();
28 | const timer = setInterval(syncLocal, PULSE_RATE);
29 | return () => clearInterval(timer);
30 | }, []);
31 |
32 | let tagText = '本地连接失败';
33 | if (status === Status.connected) {
34 | tagText = [projectName, '本地连接成功'].join(' ');
35 | }
36 |
37 | return (
38 |
39 | {tagText}
40 |
41 | );
42 | };
43 |
44 | export default ConnectStatus;
45 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/export/exportModal.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/modal/style';
4 | import styles from './index.module.less';
5 |
6 | import JSZip from 'jszip';
7 | import { Modal } from 'antd';
8 | import { DataUri, Graph } from '@antv/x6';
9 | import { compileForProject } from '@imove/compile-code';
10 |
11 | interface IExportModalProps {
12 | flowChart: Graph;
13 | visible: boolean;
14 | onClose: () => void;
15 | }
16 |
17 | const ExportModal: React.FC = (props) => {
18 | const { flowChart, visible, onClose } = props;
19 | const onExportDSL = () => {
20 | const dsl = JSON.stringify(flowChart.toJSON(), null, 2);
21 | const blob = new Blob([dsl], { type: 'text/plain' });
22 | DataUri.downloadBlob(blob, 'imove.dsl.json');
23 | };
24 | const onExportCode = () => {
25 | const zip = new JSZip();
26 | const dsl = flowChart.toJSON();
27 | const output = compileForProject(dsl);
28 | Helper.recursiveZip(zip, output);
29 | zip.generateAsync({ type: 'blob' }).then((blob) => {
30 | DataUri.downloadBlob(blob, 'logic.zip');
31 | });
32 | };
33 | const onExportFlowChart = () => {
34 | flowChart.toPNG(
35 | (dataUri: string) => {
36 | DataUri.downloadDataUri(dataUri, 'flowChart.png');
37 | },
38 | { padding: 50, ratio: '3.0' },
39 | );
40 | };
41 |
42 | return (
43 |
51 |
52 |
53 |
57 |
DSL
58 |
59 |
60 |
64 |
代码
65 |
66 |
67 |
71 |
流程图
72 |
73 |
74 |
75 | );
76 | };
77 |
78 | const Helper = {
79 | recursiveZip(root: JSZip, json: any) {
80 | if (typeof json !== 'object' || json === null) {
81 | return;
82 | }
83 | for (const key in json) {
84 | const val = json[key];
85 | if (typeof val === 'string') {
86 | root.file(key, val);
87 | } else {
88 | const dir = root.folder(key) as JSZip;
89 | Helper.recursiveZip(dir, val);
90 | }
91 | }
92 | },
93 | };
94 |
95 | export default ExportModal;
96 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/export/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-left: 20px;
3 | }
4 |
5 | .modalContent {
6 |
7 | display: flex;
8 | flex-direction: row;
9 | justify-content: space-around;
10 | width: 100%;
11 | padding: 30px 0;
12 |
13 | .downloadWrap {
14 |
15 | display: flex;
16 | flex-direction: column;
17 | justify-content: center;
18 | align-items: center;
19 | padding: 15px 20px 5px;
20 | border: 0.5px solid #EFEFEF;
21 |
22 | &:hover {
23 | cursor: pointer;
24 | background-color: #EFEFEF;
25 | }
26 |
27 | .picPreview {
28 | width: 150px;
29 | height: 150px;
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/header/export/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Button } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import ExportModal from './exportModal';
8 |
9 | interface IProps {
10 | flowChart: Graph;
11 | }
12 |
13 | const Export: React.FC = (props) => {
14 | const { flowChart } = props;
15 | const [modalVisible, setModalVisible] = useState(false);
16 |
17 | const onOpenModal = () => setModalVisible(true);
18 | const onCloseModal = () => setModalVisible(false);
19 |
20 | return (
21 |
22 |
23 | 导出
24 |
25 |
30 |
31 | );
32 | };
33 |
34 | export default Export;
35 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/guide/index.module.less:
--------------------------------------------------------------------------------
1 | .guideContainer{
2 | height:650px;
3 |
4 | .tabPane{
5 | height:600px;
6 | overflow-y:auto;
7 |
8 | &::-webkit-scrollbar {
9 | width: 0;
10 | }
11 |
12 | img{
13 | margin:10px 0;
14 | }
15 |
16 | pre{
17 | color:#c1811d;
18 | font-size:12px;
19 | line-height:13px;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/guide/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import { Button } from 'antd';
4 | import GuideModal from './guideModal';
5 |
6 | const Export: React.FC = (props) => {
7 | const [modalVisible, setModalVisible] = useState(false);
8 |
9 | const onOpenModal = () => setModalVisible(true);
10 | const onCloseModal = () => setModalVisible(false);
11 |
12 | return (
13 |
14 |
15 | 帮助指引
16 |
17 |
18 |
19 | );
20 | };
21 |
22 | export default Export;
23 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/importDSL/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-left: 20px;
3 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/header/importDSL/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 | import { Button, Upload, message } from 'antd';
7 |
8 | interface IProps {
9 | flowChart: Graph;
10 | }
11 |
12 | const ImportDSL: React.FC = (props) => {
13 | const { flowChart } = props;
14 | const [disabled, setDisabled] = useState(false);
15 | const beforeUpload = (file: any) => {
16 | setDisabled(true);
17 | const reader = new FileReader();
18 | reader.onload = (evt) => {
19 | setDisabled(false);
20 | if (!evt.target) {
21 | message.error('加载文件失败!');
22 | } else {
23 | const dsl = evt.target.result as string;
24 | try {
25 | flowChart.fromJSON(JSON.parse(dsl));
26 | } catch (err) {
27 | message.error('DSL解析失败!');
28 | }
29 | }
30 | };
31 | reader.readAsText(file);
32 | return false;
33 | };
34 |
35 | return (
36 |
43 |
44 | 导入DSL
45 |
46 |
47 | );
48 | };
49 |
50 | export default ImportDSL;
51 |
--------------------------------------------------------------------------------
/packages/core/src/mods/header/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | flex: 1;
3 | display: flex;
4 | flex-direction: row;
5 | justify-content: space-between;
6 | align-items: center;
7 | width: 100%;
8 | height: 100%;
9 | padding: 0 20px;
10 | background-color: #FEFEFE;
11 | }
12 |
13 | .titleText {
14 | font-size: 24px;
15 | font-weight: 500;
16 | }
17 |
18 | .widgets {
19 | display: flex;
20 | flex-direction: row;
21 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/header/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 | import { Modal, message } from 'antd';
7 | import Guide from './guide';
8 | import Export from './export';
9 | import ImportDSL from './importDSL';
10 | import ConnectStatus, { Status } from './connectStatus';
11 | import Configuration from './configuration';
12 | import { localConnect } from '../../api';
13 |
14 | interface IProps {
15 | flowChart: Graph;
16 | }
17 |
18 | const Header: React.FC = (props: IProps) => {
19 | const { flowChart } = props;
20 | const [projectName, setProjectName] = useState('');
21 | const [status, setStatus] = useState(Status.disconnected);
22 |
23 | // network
24 | const syncLocal = useCallback(() => {
25 | return localConnect()
26 | .then((res) => res.json())
27 | .then((data = {}) => {
28 | setStatus(Status.connected);
29 | setProjectName(data.projectName);
30 | return data;
31 | })
32 | .catch((error) => {
33 | console.log(error);
34 | setStatus(Status.disconnected);
35 | console.log('connect local failed, the error is:', error.message);
36 | });
37 | }, [setStatus, setProjectName]);
38 |
39 | const confirmToSync = useCallback(() => {
40 | return syncLocal().then((data) => {
41 | const { dsl } = data || {};
42 | if (dsl) {
43 | Modal.confirm({
44 | title: '本地连接成功,是否将数据同步至当前项目?',
45 | onOk() {
46 | try {
47 | flowChart.fromJSON(dsl);
48 | } catch (error) {
49 | message.error('同步失败!');
50 | }
51 | },
52 | });
53 | }
54 | });
55 | }, [syncLocal, flowChart]);
56 |
57 | return (
58 |
75 | );
76 | };
77 |
78 | export default Header;
79 |
--------------------------------------------------------------------------------
/packages/core/src/mods/layout/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 |
3 | display: flex;
4 | flex-direction: column;
5 | width: 100%;
6 | height: 100%;
7 |
8 | .header {
9 | width: 100%;
10 | height: 48px;
11 | border-bottom: 1px solid #D9D9D9;
12 | }
13 |
14 | .toolBar {
15 | height: 43px;
16 | }
17 |
18 | .sideBar {
19 | border-right: 1px solid #D9D9D9;
20 | }
21 |
22 | .settingBar {
23 | width: 350px;
24 | height: 100%;
25 | border-left: 1px solid #D9D9D9;
26 | }
27 | }
--------------------------------------------------------------------------------
/packages/core/src/mods/layout/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 | import SplitPane from 'react-split-pane-v2';
7 |
8 | const Pane = SplitPane.Pane;
9 |
10 | interface ISubComponentProps {
11 | flowChart: Graph;
12 | }
13 |
14 | interface IProps {
15 | flowChart: Graph | undefined;
16 | Header: React.FC;
17 | SideBar: React.FC;
18 | ToolBar: React.FC;
19 | SettingBar: React.FC;
20 | }
21 |
22 | const Layout: React.FC = (props) => {
23 | const { flowChart, Header, SideBar, ToolBar, SettingBar } = props;
24 |
25 | let header, sideBar, toolBar, settingBar;
26 | if (flowChart) {
27 | header = ;
28 | sideBar = ;
29 | toolBar = ;
30 | settingBar = ;
31 | }
32 |
33 | return (
34 |
35 |
{header}
36 |
{toolBar}
37 |
38 |
44 | {sideBar}
45 |
46 |
47 | {props.children}
48 |
54 | {settingBar}
55 |
56 |
57 |
58 |
59 | );
60 | };
61 |
62 | export default Layout;
63 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/checkbox/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 |
3 | margin-top: 20px;
4 |
5 | .titleText {
6 | font-size: 14px;
7 | margin-bottom: 10px;
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/checkbox/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/checkbox/style';
4 | import styles from './index.module.less';
5 |
6 | import { Checkbox as AntdCheckbox } from 'antd';
7 | import { CheckboxChangeEvent } from 'antd/lib/checkbox';
8 |
9 | interface IProps {
10 | value: any;
11 | name: string;
12 | title: string;
13 | disabled?: boolean;
14 | description?: string;
15 | onValueChange: (val: boolean) => void;
16 | }
17 |
18 | const Checkbox: React.FC = (props) => {
19 | const { name, title, value, disabled, onValueChange } = props;
20 | const onChange = (evt: CheckboxChangeEvent): void => {
21 | if (evt.target.checked !== value) {
22 | onValueChange(evt.target.checked);
23 | }
24 | };
25 | return (
26 |
35 | );
36 | };
37 |
38 | export default Checkbox;
39 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/input/index.module.less:
--------------------------------------------------------------------------------
1 | .titleText {
2 | font-size: 14px;
3 | width:150px;
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/input/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/input/style';
4 | import styles from './index.module.less';
5 |
6 | import { Input as AntdInput } from 'antd';
7 |
8 | interface IProps {
9 | value: any;
10 | name: string;
11 | title: string;
12 | disabled?: boolean;
13 | description?: string;
14 | onValueChange: (val: string) => void;
15 | }
16 |
17 | const Input: React.FC = (props) => {
18 | const { name, title, value, disabled, onValueChange } = props;
19 | const onChange = (evt: React.ChangeEvent): void => {
20 | if (evt.target.value !== value) {
21 | onValueChange(evt.target.value);
22 | }
23 | };
24 | return (
25 |
34 | );
35 | };
36 |
37 | export default Input;
38 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/json/index.less:
--------------------------------------------------------------------------------
1 | .ant-card-bordered{
2 | border:none;
3 | }
4 | .ant-card-head{
5 | border:none;
6 | height:35px;
7 | }
8 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/json/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: 20px;
3 |
4 | .header{
5 | display:flex;
6 | flex-direction: row;
7 | justify-content:space-between;
8 |
9 | .titleText {
10 | font-size: 14px;
11 | margin-bottom: 10px;
12 | }
13 | }
14 | }
15 |
16 | .editModal {
17 | .tabPane{
18 | .form{
19 | height:600px;
20 | overflow-y: auto;
21 | margin:0 20px;
22 |
23 | :global {
24 | .fr-wrapper {
25 | position: relative;
26 | }
27 | }
28 | }
29 | .configInput{
30 | height: 600px;
31 | }
32 | }
33 |
34 | :global {
35 | .ant-modal-body {
36 | padding: 0;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/components/json/json.ts:
--------------------------------------------------------------------------------
1 | const compData = [
2 | {
3 | text: 'Input',
4 | name: 'input',
5 | schema: {
6 | title: 'Input',
7 | type: 'string',
8 | description: '输入框',
9 | },
10 | },
11 | {
12 | text: 'Textarea',
13 | name: 'textarea',
14 | schema: {
15 | title: 'Textarea',
16 | type: 'string',
17 | format: 'textarea',
18 | description: '文本编辑框',
19 | },
20 | },
21 | {
22 | text: 'Switch',
23 | name: 'allBoolean',
24 | schema: {
25 | title: 'Switch',
26 | type: 'boolean',
27 | 'ui:widget': 'switch',
28 | description: '开关控制',
29 | },
30 | },
31 | {
32 | text: 'Select',
33 | name: 'select',
34 | schema: {
35 | title: 'Select',
36 | type: 'string',
37 | enum: ['a', 'b', 'c'],
38 | enumNames: ['早', '中', '晚'],
39 | description: '下拉单选',
40 | },
41 | },
42 | {
43 | text: 'MultiSelect',
44 | name: 'multiSelect',
45 | schema: {
46 | title: 'MultiSelect',
47 | description: '下拉多选',
48 | type: 'array',
49 | items: {
50 | type: 'string',
51 | },
52 | enum: ['A', 'B', 'C', 'D'],
53 | enumNames: ['1', '2', '3', '4'],
54 | 'ui:widget': 'multiSelect',
55 | },
56 | },
57 | {
58 | text: 'Checkbox',
59 | name: 'checkbox',
60 | schema: {
61 | title: 'Checkbox',
62 | description: '点击多选',
63 | type: 'array',
64 | items: {
65 | type: 'string',
66 | },
67 | enum: ['A', 'B', 'C', 'D'],
68 | enumNames: ['1', '2', '3', '4'],
69 | },
70 | },
71 | {
72 | text: 'TimePicker',
73 | name: 'timeSelect',
74 | schema: {
75 | title: 'TimePicker',
76 | type: 'string',
77 | format: 'time',
78 | description: '时间选择',
79 | },
80 | },
81 | {
82 | text: 'DatePicker',
83 | name: 'dateSelect',
84 | schema: {
85 | title: 'DatePicker',
86 | type: 'string',
87 | format: 'date',
88 | description: '日期选择',
89 | },
90 | },
91 | {
92 | text: 'DateRange',
93 | name: 'dateRangeSelect',
94 | schema: {
95 | title: 'DateRange',
96 | type: 'range',
97 | format: 'date',
98 | description: '日期范围选择',
99 | },
100 | },
101 | ];
102 |
103 | export { compData };
104 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 |
3 | display: flex;
4 | flex-direction: column;
5 | width: 100%;
6 | height: 100%;
7 |
8 | &.center {
9 | justify-content: center;
10 | align-items: center;
11 | }
12 |
13 | :global {
14 |
15 | .ant-tabs-nav {
16 | margin-bottom: 0;
17 | }
18 |
19 | .ant-tabs-nav-list {
20 | display: flex;
21 | flex: 1;
22 | }
23 |
24 | .ant-tabs-tab {
25 | display: flex;
26 | flex: 1;
27 | justify-content: center;
28 | align-items: center;
29 | padding: 10px 0;
30 | background-color: #FAFAFA;
31 | border-bottom: 1px solid #D9D9D9;
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useReducer } from 'react';
2 |
3 | import 'antd/es/tabs/style';
4 | import 'antd/es/empty/style';
5 | import styles from './index.module.less';
6 |
7 | import { Graph } from '@antv/x6';
8 | import { Empty, Tabs } from 'antd';
9 | import Basic from './mods/basic';
10 | // import TestCase from './mods/testCase';
11 |
12 | const { TabPane } = Tabs;
13 |
14 | interface IProps {
15 | flowChart: Graph;
16 | }
17 |
18 | const SettingBar: React.FC = (props) => {
19 | const { flowChart } = props;
20 | const forceUpdate = useReducer((n) => n + 1, 0)[1];
21 |
22 | useEffect(() => {
23 | flowChart.on('settingBar:forceUpdate', forceUpdate);
24 | return () => {
25 | flowChart.off('settingBar:forceUpdate');
26 | };
27 | }, []);
28 |
29 | const nodes = flowChart.getSelectedCells().filter((v) => v.shape !== 'edge');
30 | if (nodes.length === 1) {
31 | return (
32 |
33 |
43 |
44 |
45 |
46 | {/*
47 |
48 | */}
49 |
50 |
51 | );
52 | } else {
53 | return (
54 |
55 |
59 |
60 | );
61 | }
62 | };
63 |
64 | export default SettingBar;
65 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/mods/basic/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | height: 900px;
3 | overflow-y: auto;
4 |
5 | &::-webkit-scrollbar {
6 | display: none;
7 | }
8 |
9 | .input{
10 | margin-top:20px;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/mods/basic/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Card } from 'antd';
6 | import { Cell, Graph } from '@antv/x6';
7 | import Json from '../../components/json';
8 | import Input from '../../components/input';
9 |
10 | interface IProps {
11 | selectedCell: Cell;
12 | flowChart: Graph;
13 | }
14 |
15 | interface IBasicData {
16 | label: string;
17 | trigger?: string;
18 | dependencies: string;
19 | configSchema: string;
20 | }
21 |
22 | const Basic: React.FC = (props) => {
23 | const { selectedCell, flowChart } = props;
24 | const [data, setData] = useState(selectedCell.getData());
25 | const { label, trigger, dependencies, configSchema } = data || {};
26 |
27 | // life
28 | useEffect(() => {
29 | setData(selectedCell.getData());
30 | }, [selectedCell]);
31 | useEffect(() => {
32 | const handler = () => setData(selectedCell.getData());
33 | flowChart.on('settingBar.basicPanel:forceUpdate', handler);
34 | return () => {
35 | flowChart.off('settingBar.basicPanel:forceUpdate', handler);
36 | };
37 | }, [selectedCell]);
38 |
39 | // events
40 | const batchUpdate = (newData: { [key: string]: any }): void => {
41 | selectedCell.setData(newData);
42 | setData(Object.assign({}, data, newData));
43 | };
44 | const commonChange = (key: string, val: string): void => {
45 | batchUpdate({ [key]: val });
46 | };
47 | const onChangeLabel = (val: string): void => {
48 | commonChange('label', val);
49 | selectedCell.setAttrs({ label: { text: val } });
50 | };
51 | const onChangeConfigSchema = (val: string): void => {
52 | commonChange('configSchema', val);
53 | };
54 | const onChangeTrigger = (val: string): void => {
55 | commonChange('trigger', val);
56 | };
57 | const onChangeDependencies = (val: string): void => {
58 | commonChange('dependencies', val);
59 | };
60 |
61 | return (
62 |
97 | );
98 | };
99 |
100 | export default Basic;
101 |
--------------------------------------------------------------------------------
/packages/core/src/mods/settingBar/mods/testCase/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useCallback } from 'react';
2 |
3 | import { Modal } from 'antd';
4 | import { Graph } from '@antv/x6';
5 | import CodeRun from '../../../../components/codeRun';
6 |
7 | interface IProps {
8 | flowChart: Graph;
9 | selectedCell: any;
10 | }
11 |
12 | const TestCase: React.FC = (props) => {
13 | const { flowChart } = props;
14 | const [visible, setVisible] = useState(false);
15 |
16 | useEffect(() => {
17 | flowChart.on('settingBar:runCode', showModal);
18 | return () => {
19 | flowChart.off('settingBar:runCode', showModal);
20 | };
21 | }, [flowChart]);
22 |
23 | const showModal = useCallback(() => setVisible(true), []);
24 | const closeModal = useCallback(() => setVisible(false), []);
25 |
26 | return (
27 |
28 | );
29 | };
30 |
31 | interface IEditModalProps {
32 | visible: boolean;
33 | flowChart: Graph;
34 | onClose: () => void;
35 | }
36 |
37 | const EditModal: React.FC = (props): JSX.Element => {
38 | const { visible, flowChart, onClose } = props;
39 |
40 | return (
41 |
50 |
51 |
52 | );
53 | };
54 |
55 | export default TestCase;
56 |
--------------------------------------------------------------------------------
/packages/core/src/mods/sideBar/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 | width: 100%;
3 |
4 | :global {
5 | .ant-collapse > .ant-collapse-item > .ant-collapse-header {
6 | padding: 10px 16px 10px 40px;
7 | }
8 | }
9 |
10 | .collapse {
11 | border: 0;
12 | border-radius: 0;
13 | }
14 | }
15 |
16 | .panelContent {
17 | display: flex;
18 | flex-direction: row;
19 | flex-wrap: wrap;
20 |
21 | .cellWrapper {
22 | display: flex;
23 | justify-content: center;
24 | align-items: center;
25 | margin: 8px;
26 | width: 60px;
27 | height: 60px;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/core/src/mods/sideBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useMemo } from 'react';
2 |
3 | import 'antd/es/collapse/style';
4 | import styles from './index.module.less';
5 |
6 | import { Collapse } from 'antd';
7 | import { Addon, Graph, Node } from '@antv/x6';
8 | import cellMap from '../../common/previewCell';
9 |
10 | const { Dnd } = Addon;
11 | const { Panel } = Collapse;
12 | const GENERAL_GROUP = {
13 | key: 'general',
14 | name: '通用元件',
15 | cellTypes: ['imove-start', 'imove-branch', 'imove-behavior'],
16 | };
17 |
18 | interface IGroupItem {
19 | key: string;
20 | name: string;
21 | cellTypes: string[];
22 | }
23 |
24 | interface ISideBarProps {
25 | flowChart: Graph;
26 | }
27 |
28 | const SideBar: React.FC = (props) => {
29 | const { flowChart } = props;
30 | const [groups, setGroups] = useState([]);
31 | const dnd = useMemo(() => new Dnd({ target: flowChart, scaled: true }), [
32 | flowChart,
33 | ]);
34 |
35 | // life
36 | useEffect(() => {
37 | // TODO: fetch to get custom group data
38 | setGroups([GENERAL_GROUP]);
39 | }, []);
40 |
41 | return (
42 |
43 |
47 | {groups.map((group) => (
48 |
49 |
50 |
51 | ))}
52 |
53 |
54 | );
55 | };
56 |
57 | interface IPanelContentProps {
58 | dnd: Addon.Dnd;
59 | cellTypes: string[];
60 | }
61 |
62 | const PanelContent: React.FC = (props) => {
63 | const { dnd, cellTypes } = props;
64 | const onMouseDown = (evt: any, cellType: string) => {
65 | dnd.start(Node.create({ shape: cellType }), evt);
66 | };
67 | return (
68 |
69 | {cellTypes.map((cellType, index) => {
70 | const Component = cellMap[cellType];
71 | return (
72 |
73 | onMouseDown(evt, cellType)} />
74 |
75 | );
76 | })}
77 |
78 | );
79 | };
80 |
81 | export default SideBar;
82 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/index.module.less:
--------------------------------------------------------------------------------
1 | .container {
2 |
3 | display: flex;
4 | flex-direction: row;
5 | justify-content: flex-start;
6 | align-items: center;
7 | width: 100%;
8 | height: 100%;
9 | background-color: #FAFAFA;
10 | border-bottom: 1px solid #D9D9D9;
11 |
12 | .group {
13 | display: flex;
14 | flex-direction: row;
15 | align-items: center;
16 | padding: 0 4px;
17 | height: 100%;
18 | border-right: 1px solid #D9D9D9;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useReducer } from 'react';
2 |
3 | import 'antd/es/tooltip/style';
4 | import styles from './index.module.less';
5 |
6 | import { Graph } from '@antv/x6';
7 | import widgets from './widgets';
8 | import ModifyStatus from './widgets/modifyStatus';
9 |
10 | interface IProps {
11 | flowChart: Graph;
12 | }
13 |
14 | const ToolBar: React.FC = (props) => {
15 | const { flowChart } = props;
16 | const forceUpdate = useReducer((n) => n + 1, 0)[1];
17 |
18 | useEffect(() => {
19 | flowChart.on('toolBar:forceUpdate', forceUpdate);
20 | return () => {
21 | flowChart.off('toolBar:forceUpdate');
22 | };
23 | }, []);
24 |
25 | return (
26 |
27 | {widgets.map((group, index) => (
28 |
29 | {group.map((ToolItem, index) => {
30 | return ;
31 | })}
32 |
33 | ))}
34 |
35 |
36 | );
37 | };
38 |
39 | export default ToolBar;
40 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/bgColor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/menu/style';
4 | import styles from './index.module.less';
5 |
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import XIcon from '../../../components/xIcon';
9 | import ColorPicker from '../../../components/colorPicker';
10 | import makeDropdownWidget from './common/makeDropdownWidget';
11 | import {
12 | hasNodeSelected,
13 | getSelectedNodes,
14 | } from '../../../utils/flowChartUtils';
15 |
16 | interface IProps {
17 | flowChart: Graph;
18 | }
19 |
20 | const options = {
21 | tooltip: '填充颜色',
22 | getCurBgColor(flowChart: Graph) {
23 | let bgColor = '#DDD';
24 | const nodes = getSelectedNodes(flowChart);
25 | if (!options.disabled(flowChart) && nodes.length > 0) {
26 | bgColor = safeGet(nodes, '0.attrs.body.fill', '#575757');
27 | }
28 | return bgColor;
29 | },
30 | getIcon(flowChart: Graph) {
31 | const bgColor = options.getCurBgColor(flowChart);
32 | return (
33 |
40 | );
41 | },
42 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
43 | const bgColor = options.getCurBgColor(flowChart);
44 | const onChangeComplete = (color: string) => onChange(color);
45 | return ;
46 | },
47 | handler: (flowChart: Graph, value: any) => {
48 | getSelectedNodes(flowChart).forEach((node) =>
49 | node.setAttrs({ body: { fill: value } }),
50 | );
51 | },
52 | disabled(flowChart: Graph) {
53 | return !hasNodeSelected(flowChart);
54 | },
55 | };
56 |
57 | const BgColor: React.FC = makeDropdownWidget(options);
58 |
59 | export default BgColor;
60 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/bold.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { safeGet } from '../../../utils';
5 | import { BoldOutlined } from '@ant-design/icons';
6 | import shortcuts from '../../../common/shortcuts';
7 | import makeBtnWidget from './common/makeBtnWidget';
8 |
9 | interface IProps {
10 | flowChart: Graph;
11 | }
12 |
13 | const Bold: React.FC = makeBtnWidget({
14 | tooltip: '加粗',
15 | handler: shortcuts.bold.handler,
16 | getIcon() {
17 | return ;
18 | },
19 | disabled(flowChart: Graph) {
20 | return flowChart.getSelectedCellCount() === 0;
21 | },
22 | selected(flowChart: Graph) {
23 | const cells = flowChart.getSelectedCells();
24 | if (cells.length > 0) {
25 | return safeGet(cells, '0.attrs.label.fontWeight', 'normal') === 'bold';
26 | } else {
27 | return false;
28 | }
29 | },
30 | });
31 |
32 | export default Bold;
33 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/borderColor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/menu/style';
4 | import styles from './index.module.less';
5 |
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import { HighlightOutlined } from '@ant-design/icons';
9 | import ColorPicker from '../../../components/colorPicker';
10 | import makeDropdownWidget from './common/makeDropdownWidget';
11 | import {
12 | hasNodeSelected,
13 | getSelectedNodes,
14 | } from '../../../utils/flowChartUtils';
15 |
16 | interface IProps {
17 | flowChart: Graph;
18 | }
19 |
20 | const options = {
21 | tooltip: '边框颜色',
22 | getCurBorderColor(flowChart: Graph) {
23 | let borderColor = '#DDD';
24 | const nodes = getSelectedNodes(flowChart);
25 | if (!options.disabled(flowChart) && nodes.length > 0) {
26 | borderColor = safeGet(nodes, '0.attrs.body.stroke', '#333');
27 | }
28 | return borderColor;
29 | },
30 | getIcon(flowChart: Graph) {
31 | const borderColor = options.getCurBorderColor(flowChart);
32 | return (
33 |
40 | );
41 | },
42 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
43 | const borderColor = options.getCurBorderColor(flowChart);
44 | const onChangeComplete = (color: string) => onChange(color);
45 | return (
46 |
47 | );
48 | },
49 | handler: (flowChart: Graph, value: any) => {
50 | getSelectedNodes(flowChart).forEach((node) =>
51 | node.setAttrs({ body: { stroke: value } }),
52 | );
53 | },
54 | disabled(flowChart: Graph) {
55 | return !hasNodeSelected(flowChart);
56 | },
57 | };
58 |
59 | const BorderColor: React.FC = makeDropdownWidget(options);
60 |
61 | export default BorderColor;
62 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/bringToBack.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import XIcon from '../../../components/xIcon';
5 | import shortcuts from '../../../common/shortcuts';
6 | import makeBtnWidget from './common/makeBtnWidget';
7 | import { hasNodeSelected } from '../../../utils/flowChartUtils';
8 |
9 | interface IProps {
10 | flowChart: Graph;
11 | }
12 |
13 | const BringToBack: React.FC = makeBtnWidget({
14 | tooltip: '置于底层',
15 | handler: shortcuts.bringToBack.handler,
16 | getIcon() {
17 | return ;
18 | },
19 | disabled(flowChart: Graph) {
20 | return !hasNodeSelected(flowChart);
21 | },
22 | });
23 |
24 | export default BringToBack;
25 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/bringToTop.tsx:
--------------------------------------------------------------------------------
1 | // at.alicdn.com/t/font_2024452_s39le1337k9.js
2 | import React from 'react';
3 |
4 | import { Graph } from '@antv/x6';
5 | import XIcon from '../../../components/xIcon';
6 | import shortcuts from '../../../common/shortcuts';
7 | import makeBtnWidget from './common/makeBtnWidget';
8 | import { hasNodeSelected } from '../../../utils/flowChartUtils';
9 |
10 | interface IProps {
11 | flowChart: Graph;
12 | }
13 |
14 | const BringToTop: React.FC = makeBtnWidget({
15 | tooltip: '置于顶层',
16 | handler: shortcuts.bringToTop.handler,
17 | getIcon() {
18 | return ;
19 | },
20 | disabled(flowChart: Graph) {
21 | return !hasNodeSelected(flowChart);
22 | },
23 | });
24 |
25 | export default BringToTop;
26 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/common/makeBtnWidget.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 |
3 | import 'antd/es/tooltip/style';
4 | import styles from '../index.module.less';
5 |
6 | import { Tooltip } from 'antd';
7 | import { Graph } from '@antv/x6';
8 |
9 | interface IOptions {
10 | tooltip: string;
11 | getIcon: (flowChart: Graph) => ReactElement;
12 | handler: (flowChart: Graph) => void;
13 | disabled?: (flowChart: Graph) => boolean;
14 | selected?: (flowChart: Graph) => boolean;
15 | }
16 |
17 | interface IBtnWidgetProps {
18 | flowChart: Graph;
19 | }
20 |
21 | const makeBtnWidget = (options: IOptions) => {
22 | const Widget: React.FC = (props) => {
23 | const { flowChart } = props;
24 | const { tooltip, getIcon, handler } = options;
25 | const iconWrapperCls = [styles.btnWidget];
26 | let { disabled = false, selected = false } = options;
27 | if (typeof disabled === 'function') {
28 | disabled = disabled(flowChart);
29 | disabled && iconWrapperCls.push(styles.disabled);
30 | }
31 | if (typeof selected === 'function') {
32 | selected = selected(flowChart);
33 | selected && iconWrapperCls.push(styles.selected);
34 | }
35 | const onClick = (): void => {
36 | if (disabled) return;
37 | handler(flowChart);
38 | flowChart.trigger('toolBar:forceUpdate');
39 | };
40 | return (
41 |
42 |
43 | {getIcon(flowChart)}
44 |
45 |
46 | );
47 | };
48 | return Widget;
49 | };
50 |
51 | export default makeBtnWidget;
52 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/common/makeDropdownWidget.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 |
3 | import 'antd/es/tooltip/style';
4 | import 'antd/es/dropdown/style';
5 | import styles from '../index.module.less';
6 |
7 | import { Graph } from '@antv/x6';
8 | import { Tooltip, Dropdown } from 'antd';
9 | import { CaretDownOutlined } from '@ant-design/icons';
10 |
11 | interface IOptions {
12 | tooltip: string;
13 | getIcon: (flowChart: Graph) => ReactElement;
14 | getOverlay: (flowChart: Graph, onChange: (data: any) => void) => ReactElement;
15 | handler: (flowChart: Graph, data: any) => void;
16 | disabled?: (flowChart: Graph) => boolean;
17 | }
18 |
19 | interface IDropdownWidgetProps {
20 | flowChart: Graph;
21 | }
22 |
23 | const makeDropdownWidget = (options: IOptions) => {
24 | const Widget: React.FC = (props) => {
25 | const { flowChart } = props;
26 | const { tooltip, getIcon, getOverlay, handler } = options;
27 | const iconWrapperCls = [styles.btnWidget];
28 | let { disabled = false } = options;
29 | if (typeof disabled === 'function') {
30 | disabled = disabled(flowChart);
31 | disabled && iconWrapperCls.push(styles.disabled);
32 | }
33 | const onChange = (data: any): void => {
34 | if (disabled) return;
35 | handler(flowChart, data);
36 | flowChart.trigger('toolBar:forceUpdate');
37 | };
38 | return (
39 |
40 |
45 |
46 | {getIcon(flowChart)}
47 |
48 |
49 |
50 | );
51 | };
52 | return Widget;
53 | };
54 |
55 | export default makeDropdownWidget;
56 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/fitWindow.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import makeBtnWidget from './common/makeBtnWidget';
5 | import { FullscreenExitOutlined } from '@ant-design/icons';
6 |
7 | interface IProps {
8 | flowChart: Graph;
9 | }
10 |
11 | const FitWindow: React.FC = makeBtnWidget({
12 | tooltip: '适配窗口',
13 | getIcon() {
14 | return ;
15 | },
16 | handler(flowChart: Graph) {
17 | flowChart.zoomToFit({ minScale: 0.5, maxScale: 1 });
18 | },
19 | });
20 |
21 | export default FitWindow;
22 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/fontSize.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/menu/style';
4 |
5 | import { Menu } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import makeDropdownWidget from './common/makeDropdownWidget';
9 |
10 | interface IProps {
11 | flowChart: Graph;
12 | }
13 |
14 | const MenuItem = Menu.Item;
15 | const FONT_SIZE_SET = [9, 10, 11, 12, 13, 14, 15, 16, 19, 22, 24, 29, 32];
16 |
17 | const FontSize: React.FC = makeDropdownWidget({
18 | tooltip: '字号',
19 | getIcon(flowChart: Graph) {
20 | let fontSize = 14;
21 | const cells = flowChart.getSelectedCells();
22 | if (cells.length > 0) {
23 | fontSize = safeGet(cells, '0.attrs.label.fontSize', 14);
24 | }
25 | return {fontSize}px ;
26 | },
27 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
28 | return (
29 | onChange(args.key)}>
30 | {FONT_SIZE_SET.map((fontSize) => (
31 |
32 | {fontSize} px
33 |
34 | ))}
35 |
36 | );
37 | },
38 | handler: (flowChart: Graph, value: any) => {
39 | flowChart
40 | .getSelectedCells()
41 | .forEach((cell) => cell.setAttrs({ label: { fontSize: value } }));
42 | },
43 | disabled(flowChart: Graph) {
44 | return flowChart.getSelectedCellCount() === 0;
45 | },
46 | });
47 |
48 | export default FontSize;
49 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/horizontalAlign.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 |
3 | import 'antd/es/menu/style';
4 |
5 | import { Menu } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import makeDropdownWidget from './common/makeDropdownWidget';
9 | import {
10 | hasNodeSelected,
11 | getSelectedNodes,
12 | } from '../../../utils/flowChartUtils';
13 | import {
14 | AlignLeftOutlined,
15 | AlignCenterOutlined,
16 | AlignRightOutlined,
17 | } from '@ant-design/icons';
18 |
19 | interface IProps {
20 | flowChart: Graph;
21 | }
22 |
23 | interface AlignItem {
24 | text: string;
25 | icon: ReactElement;
26 | attrs: {
27 | refX: number;
28 | refX2?: number;
29 | textAnchor: string;
30 | align: {
31 | horizontal: string;
32 | };
33 | };
34 | }
35 |
36 | const MenuItem = Menu.Item;
37 | const ALIGN_MAP: { [key: string]: AlignItem } = {
38 | left: {
39 | text: '左对齐',
40 | icon: ,
41 | attrs: {
42 | refX: 0,
43 | refX2: 5,
44 | textAnchor: 'start',
45 | align: {
46 | horizontal: 'left',
47 | },
48 | },
49 | },
50 | center: {
51 | text: '居中对齐',
52 | icon: ,
53 | attrs: {
54 | refX: 0.5,
55 | refX2: 0,
56 | textAnchor: 'middle',
57 | align: {
58 | horizontal: 'center',
59 | },
60 | },
61 | },
62 | right: {
63 | text: '右对齐',
64 | icon: ,
65 | attrs: {
66 | refX: 0.99,
67 | refX2: -5,
68 | textAnchor: 'end',
69 | align: {
70 | horizontal: 'right',
71 | },
72 | },
73 | },
74 | };
75 |
76 | const HorizontalAlign: React.FC = makeDropdownWidget({
77 | tooltip: '水平对齐',
78 | getIcon(flowChart: Graph) {
79 | let alignType = 'center';
80 | const nodes = getSelectedNodes(flowChart);
81 | if (nodes.length > 0) {
82 | alignType = safeGet(nodes, '0.attrs.label.align.horizontal', 'center');
83 | }
84 | return ALIGN_MAP[alignType].icon;
85 | },
86 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
87 | return (
88 | onChange(ALIGN_MAP[key].attrs)}>
89 | {Object.keys(ALIGN_MAP).map((alignType) => (
90 |
91 | {ALIGN_MAP[alignType].icon}
92 | {ALIGN_MAP[alignType].text}
93 |
94 | ))}
95 |
96 | );
97 | },
98 | handler: (flowChart: Graph, value: any) => {
99 | getSelectedNodes(flowChart).forEach((node) =>
100 | node.setAttrs({ label: value }),
101 | );
102 | },
103 | disabled(flowChart: Graph) {
104 | return !hasNodeSelected(flowChart);
105 | },
106 | });
107 |
108 | export default HorizontalAlign;
109 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/index.module.less:
--------------------------------------------------------------------------------
1 | .btnWidget {
2 |
3 | box-sizing: border-box;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | padding: 0 8px;
8 | min-width: 34px;
9 | height: 34px;
10 | font-size: 16px;
11 | border-radius: 2px;
12 | border: 0.5px solid transparent;
13 |
14 | .caret {
15 | margin-left: 4px;
16 | font-size: 10px;
17 | }
18 |
19 | &:hover {
20 | cursor: pointer;
21 | border: 0.5px solid #CCC;
22 | }
23 |
24 | &.selected {
25 | border: 0.5px solid #CCC;
26 | box-shadow: inset 0 1px 2px rgba(0,0,0,.1);
27 | background-color: #EEE;
28 | background-image: linear-gradient(top,#eee,#e0e0e0);
29 | }
30 |
31 | &.disabled {
32 | color: #CCC;
33 | }
34 |
35 | &.disabled:hover {
36 | cursor: default;
37 | border: 0.5px solid transparent;
38 | }
39 | }
40 |
41 | .zoomContainer {
42 |
43 | display: flex;
44 | flex-direction: row;
45 | align-items: center;
46 |
47 | .zoomText {
48 | width: 45px;
49 | text-align: center;
50 | }
51 | }
52 |
53 | .modifyStatusContainer {
54 | margin-left: 8px;
55 | }
56 |
57 | .bgColorContainer, .textColorContainer, .borderColorContainer {
58 |
59 | position: relative;
60 | display: flex;
61 | flex-direction: column;
62 | justify-content: center;
63 |
64 | .fillIcon {
65 | position: relative;
66 | top: -2px;
67 | font-size: 15px;
68 | }
69 |
70 | .textIcon {
71 | position: relative;
72 | top: -2px;
73 | font-size: 15px;
74 | }
75 |
76 | .borderColorIcon {
77 | position: relative;
78 | top: -2px;
79 | font-size: 15px;
80 | }
81 |
82 | .colorPreview {
83 | position: absolute;
84 | bottom: 0;
85 | left: 0;
86 | right: 0;
87 | height: 1.6px;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 |
5 | import Save from './save';
6 | import Undo from './undo';
7 | import Redo from './redo';
8 | import Zoom from './zoom';
9 | import Bold from './bold';
10 | import Italic from './italic';
11 | import BgColor from './bgColor';
12 | import FontSize from './fontSize';
13 | import TextColor from './textColor';
14 | import LineStyle from './lineStyle';
15 | import Underline from './underline';
16 | import NodeAlign from './nodeAlign';
17 | import FitWindow from './fitWindow';
18 | import BringToTop from './bringToTop';
19 | import BringToBack from './bringToBack';
20 | import BorderColor from './borderColor';
21 | import VerticalAlign from './verticalAlign';
22 | import HorizontalAlign from './horizontalAlign';
23 |
24 | interface IProps {
25 | flowChart: Graph;
26 | }
27 |
28 | const tools: React.FC[][] = [
29 | [Save, FitWindow, Undo, Redo],
30 | [Zoom],
31 | [FontSize, Bold, Italic, Underline],
32 | [TextColor, BgColor, BorderColor, LineStyle],
33 | [HorizontalAlign, VerticalAlign],
34 | [NodeAlign, BringToTop, BringToBack],
35 | ];
36 |
37 | export default tools;
38 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/italic.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { safeGet } from '../../../utils';
5 | import makeBtnWidget from './common/makeBtnWidget';
6 | import { ItalicOutlined } from '@ant-design/icons';
7 | import shortcuts from '../../../common/shortcuts';
8 |
9 | interface IProps {
10 | flowChart: Graph;
11 | }
12 |
13 | const Italic: React.FC = makeBtnWidget({
14 | tooltip: '斜体',
15 | handler: shortcuts.italic.handler,
16 | getIcon() {
17 | return ;
18 | },
19 | disabled(flowChart: Graph) {
20 | return flowChart.getSelectedCellCount() === 0;
21 | },
22 | selected(flowChart: Graph) {
23 | const cells = flowChart.getSelectedCells();
24 | if (cells.length > 0) {
25 | return safeGet(cells, '0.attrs.label.fontStyle', 'normal') === 'italic';
26 | } else {
27 | return false;
28 | }
29 | },
30 | });
31 |
32 | export default Italic;
33 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/lineStyle.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 |
3 | import 'antd/es/menu/style';
4 |
5 | import { Menu } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import XIcon from '../../../components/xIcon';
9 | import makeDropdownWidget from './common/makeDropdownWidget';
10 | import {
11 | hasEdgeSelected,
12 | getSelectedEdges,
13 | } from '../../../utils/flowChartUtils';
14 |
15 | interface IProps {
16 | flowChart: Graph;
17 | }
18 |
19 | interface LineStyleItem {
20 | text: string;
21 | icon: ReactElement;
22 | attrs: {
23 | type: string;
24 | strokeDasharray: string;
25 | };
26 | }
27 |
28 | const MenuItem = Menu.Item;
29 | const LINE_STYLE_MAP: { [key: string]: LineStyleItem } = {
30 | straight: {
31 | text: '直线',
32 | icon: ,
33 | attrs: {
34 | type: 'straight',
35 | strokeDasharray: '5, 0',
36 | },
37 | },
38 | dashed: {
39 | text: '虚线',
40 | icon: ,
41 | attrs: {
42 | type: 'dashed',
43 | strokeDasharray: '5, 5',
44 | },
45 | },
46 | };
47 |
48 | const LineStyle: React.FC = makeDropdownWidget({
49 | tooltip: '线条样式',
50 | getIcon(flowChart: Graph) {
51 | let lineType = 'straight';
52 | const edges = getSelectedEdges(flowChart);
53 | if (edges.length > 0) {
54 | lineType = safeGet(edges, '0.attrs.line.type', 'straight');
55 | }
56 | return LINE_STYLE_MAP[lineType].icon;
57 | },
58 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
59 | return (
60 | onChange(LINE_STYLE_MAP[key].attrs)}>
61 | {Object.keys(LINE_STYLE_MAP).map((lineType: string) => (
62 |
63 | {LINE_STYLE_MAP[lineType].icon}
64 | {LINE_STYLE_MAP[lineType].text}
65 |
66 | ))}
67 |
68 | );
69 | },
70 | handler: (flowChart: Graph, value: any) => {
71 | getSelectedEdges(flowChart).forEach((edge) =>
72 | edge.setAttrs({ line: value }),
73 | );
74 | },
75 | disabled(flowChart: Graph) {
76 | return !hasEdgeSelected(flowChart);
77 | },
78 | });
79 |
80 | export default LineStyle;
81 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/modifyStatus.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 |
7 | enum Status {
8 | pending = 0,
9 | syncing = 1,
10 | successful = 2,
11 | failed = 3,
12 | }
13 |
14 | interface IProps {
15 | flowChart: Graph;
16 | }
17 |
18 | const statusMap = {
19 | [Status.pending]: {
20 | color: '',
21 | text: '',
22 | },
23 | [Status.syncing]: {
24 | color: '#999',
25 | text: '正在保存...',
26 | },
27 | [Status.successful]: {
28 | color: '#999',
29 | text: '所有更改已保存',
30 | },
31 | [Status.failed]: {
32 | color: '#EC5B56',
33 | text: '同步失败,进入离线模式',
34 | },
35 | };
36 |
37 | const ModifyStatus: React.FC = (props) => {
38 | const { flowChart } = props;
39 | const [status, setStatus] = useState(Status.pending);
40 |
41 | useEffect(() => {
42 | flowChart.on('graph:change:modify', () => {
43 | setStatus(Status.syncing);
44 | });
45 | flowChart.on('graph:modified', (res: any) => {
46 | const { success } = res;
47 | if (success) {
48 | setStatus(Status.successful);
49 | } else {
50 | setStatus(Status.failed);
51 | }
52 | });
53 | return () => {
54 | flowChart.off('graph:change:modify');
55 | flowChart.off('graph:modified');
56 | };
57 | }, []);
58 |
59 | const { color, text } = statusMap[status];
60 | return status === Status.pending ? null : (
61 |
62 | {text}
63 |
64 | );
65 | };
66 |
67 | export default ModifyStatus;
68 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/redo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { RedoOutlined } from '@ant-design/icons';
5 | import shortcuts from '../../../common/shortcuts';
6 | import makeBtnWidget from './common/makeBtnWidget';
7 |
8 | interface IProps {
9 | flowChart: Graph;
10 | }
11 |
12 | const Save: React.FC = makeBtnWidget({
13 | tooltip: '重做',
14 | handler: shortcuts.redo.handler,
15 | getIcon() {
16 | return ;
17 | },
18 | disabled(flowChart: Graph) {
19 | return !flowChart.canRedo();
20 | },
21 | });
22 |
23 | export default Save;
24 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/save.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { SaveOutlined } from '@ant-design/icons';
5 | import shortcuts from '../../../common/shortcuts';
6 | import makeBtnWidget from './common/makeBtnWidget';
7 |
8 | interface IProps {
9 | flowChart: Graph;
10 | }
11 |
12 | const Save: React.FC = makeBtnWidget({
13 | tooltip: '保存',
14 | handler: shortcuts.save.handler,
15 | getIcon() {
16 | return ;
17 | },
18 | });
19 |
20 | export default Save;
21 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/textColor.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import 'antd/es/menu/style';
4 | import styles from './index.module.less';
5 |
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import XIcon from '../../../components/xIcon';
9 | import ColorPicker from '../../../components/colorPicker';
10 | import makeDropdownWidget from './common/makeDropdownWidget';
11 | import {
12 | hasNodeSelected,
13 | getSelectedNodes,
14 | } from '../../../utils/flowChartUtils';
15 |
16 | interface IProps {
17 | flowChart: Graph;
18 | }
19 |
20 | const options = {
21 | tooltip: '文本颜色',
22 | getCurTextColor(flowChart: Graph) {
23 | let textColor = '#DDD';
24 | const nodes = getSelectedNodes(flowChart);
25 | if (nodes.length > 0) {
26 | textColor = safeGet(nodes, '0.attrs.label.fill', '#575757');
27 | }
28 | return textColor;
29 | },
30 | getIcon(flowChart: Graph) {
31 | const textColor = options.getCurTextColor(flowChart);
32 | return (
33 |
40 | );
41 | },
42 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
43 | const textColor = options.getCurTextColor(flowChart);
44 | const onChangeComplete = (color: string) => onChange(color);
45 | return (
46 |
47 | );
48 | },
49 | handler: (flowChart: Graph, value: any) => {
50 | flowChart
51 | .getSelectedCells()
52 | .forEach((node) => node.setAttrs({ label: { fill: value } }));
53 | },
54 | disabled(flowChart: Graph) {
55 | return !hasNodeSelected(flowChart);
56 | },
57 | };
58 |
59 | const TextColor: React.FC = makeDropdownWidget(options);
60 |
61 | export default TextColor;
62 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/underline.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { safeGet } from '../../../utils';
5 | import shortcuts from '../../../common/shortcuts';
6 | import makeBtnWidget from './common/makeBtnWidget';
7 | import { UnderlineOutlined } from '@ant-design/icons';
8 |
9 | interface IProps {
10 | flowChart: Graph;
11 | }
12 |
13 | const Underline: React.FC = makeBtnWidget({
14 | tooltip: '下划线',
15 | handler: shortcuts.underline.handler,
16 | getIcon() {
17 | return ;
18 | },
19 | disabled(flowChart: Graph) {
20 | return flowChart.getSelectedCellCount() === 0;
21 | },
22 | selected(flowChart: Graph) {
23 | const cells = flowChart.getSelectedCells();
24 | if (cells.length > 0) {
25 | return (
26 | safeGet(cells, '0.attrs.label.textDecoration', 'none') === 'underline'
27 | );
28 | } else {
29 | return false;
30 | }
31 | },
32 | });
33 |
34 | export default Underline;
35 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/undo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Graph } from '@antv/x6';
4 | import { UndoOutlined } from '@ant-design/icons';
5 | import shortcuts from '../../../common/shortcuts';
6 | import makeBtnWidget from './common/makeBtnWidget';
7 |
8 | interface IProps {
9 | flowChart: Graph;
10 | }
11 |
12 | const Save: React.FC = makeBtnWidget({
13 | tooltip: '撤销',
14 | handler: shortcuts.undo.handler,
15 | getIcon() {
16 | return ;
17 | },
18 | disabled(flowChart: Graph) {
19 | return !flowChart.canUndo();
20 | },
21 | });
22 |
23 | export default Save;
24 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/verticalAlign.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactElement } from 'react';
2 |
3 | import 'antd/es/menu/style';
4 |
5 | import { Menu } from 'antd';
6 | import { Graph } from '@antv/x6';
7 | import { safeGet } from '../../../utils';
8 | import makeDropdownWidget from './common/makeDropdownWidget';
9 | import {
10 | hasNodeSelected,
11 | getSelectedNodes,
12 | } from '../../../utils/flowChartUtils';
13 | import {
14 | VerticalAlignTopOutlined,
15 | VerticalAlignMiddleOutlined,
16 | VerticalAlignBottomOutlined,
17 | } from '@ant-design/icons';
18 |
19 | interface IProps {
20 | flowChart: Graph;
21 | }
22 |
23 | interface AlignItem {
24 | text: string;
25 | icon: ReactElement;
26 | attrs: {
27 | refY: number;
28 | refY2?: number;
29 | textVerticalAlign: string;
30 | align: {
31 | vertical: string;
32 | };
33 | };
34 | }
35 |
36 | const MenuItem = Menu.Item;
37 | const ALIGN_MAP: { [key: string]: AlignItem } = {
38 | top: {
39 | text: '上对齐',
40 | icon: ,
41 | attrs: {
42 | refY: 0,
43 | refY2: 10,
44 | textVerticalAlign: 'start',
45 | align: {
46 | vertical: 'top',
47 | },
48 | },
49 | },
50 | center: {
51 | text: '居中对齐',
52 | icon: ,
53 | attrs: {
54 | refY: 0.5,
55 | refY2: 0,
56 | textVerticalAlign: 'middle',
57 | align: {
58 | vertical: 'center',
59 | },
60 | },
61 | },
62 | bottom: {
63 | text: '下对齐',
64 | icon: ,
65 | attrs: {
66 | refY: 0.99,
67 | refY2: -10,
68 | textVerticalAlign: 'end',
69 | align: {
70 | vertical: 'bottom',
71 | },
72 | },
73 | },
74 | };
75 |
76 | const VerticalAlign: React.FC = makeDropdownWidget({
77 | tooltip: '垂直对齐',
78 | getIcon(flowChart: Graph) {
79 | let alignType = 'center';
80 | const nodes = getSelectedNodes(flowChart);
81 | if (nodes.length > 0) {
82 | alignType = safeGet(nodes, '0.attrs.label.align.vertical', 'center');
83 | }
84 | return ALIGN_MAP[alignType].icon;
85 | },
86 | getOverlay(flowChart: Graph, onChange: (data: any) => void) {
87 | return (
88 | onChange(ALIGN_MAP[key].attrs)}>
89 | {Object.keys(ALIGN_MAP).map((alignType: string) => (
90 |
91 | {ALIGN_MAP[alignType].icon}
92 | {ALIGN_MAP[alignType].text}
93 |
94 | ))}
95 |
96 | );
97 | },
98 | handler: (flowChart: Graph, value: any) => {
99 | getSelectedNodes(flowChart).forEach((node) =>
100 | node.setAttrs({ label: value }),
101 | );
102 | },
103 | disabled(flowChart: Graph) {
104 | return !hasNodeSelected(flowChart);
105 | },
106 | });
107 |
108 | export default VerticalAlign;
109 |
--------------------------------------------------------------------------------
/packages/core/src/mods/toolBar/widgets/zoom.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | import styles from './index.module.less';
4 |
5 | import { Graph } from '@antv/x6';
6 | import shortcuts from '../../../common/shortcuts';
7 | import makeBtnWidget from './common/makeBtnWidget';
8 | import { ZoomOutOutlined, ZoomInOutlined } from '@ant-design/icons';
9 |
10 | interface IProps {
11 | flowChart: Graph;
12 | }
13 |
14 | const MIN_ZOOM = 0.5;
15 | const MAX_ZOOM = 1.5;
16 |
17 | const ZoomOut: React.FC = makeBtnWidget({
18 | tooltip: '缩小',
19 | handler: shortcuts.zoomOut.handler,
20 | getIcon() {
21 | return ;
22 | },
23 | disabled(flowChart: Graph) {
24 | return flowChart.zoom() <= MIN_ZOOM;
25 | },
26 | });
27 |
28 | const ZoomIn: React.FC = makeBtnWidget({
29 | tooltip: '放大',
30 | handler: shortcuts.zoomIn.handler,
31 | getIcon() {
32 | return ;
33 | },
34 | disabled(flowChart: Graph) {
35 | return flowChart.zoom() >= MAX_ZOOM;
36 | },
37 | });
38 |
39 | const Zoom: React.FC = (props) => {
40 | const { flowChart } = props;
41 | const [scale, setScale] = useState(flowChart.zoom());
42 | useEffect(() => {
43 | flowChart.on('scale', () => {
44 | setScale(flowChart.zoom());
45 | });
46 | }, [flowChart]);
47 | return (
48 |
49 |
50 | {Helper.scaleFormatter(scale)}
51 |
52 |
53 | );
54 | };
55 |
56 | const Helper = {
57 | scaleFormatter(scale: number): string {
58 | return (scale * 100).toFixed(0) + '%';
59 | },
60 | };
61 |
62 | export default Zoom;
63 |
--------------------------------------------------------------------------------
/packages/core/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.module.css' {
2 | const classes: { [key: string]: string };
3 | export default classes;
4 | }
5 |
6 | declare module '*.less' {
7 | const classes: { [key: string]: string };
8 | export default classes;
9 | }
10 |
11 | declare module 'fr-generator' {
12 | import React from 'react';
13 | export interface FRProps {
14 | settings?: object;
15 | }
16 | class FormRender extends React.Component {}
17 | export default FRGenerator;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/utils/analyzeDeps.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { safeGet } from './index';
3 | import { getLocalConfig } from '../api';
4 |
5 | const regex = /import\s([\s\S]*?)\sfrom\s('|")((@\w[\w\.\-]+\/)?(\w[\w\.\-]+))(\/[\w\.\-]+)*\2/gm;
6 | // (1 ) (2 )(3(4 ) (5 ))(6 )
7 |
8 | const extractPkgs = (code: string, excludePkgs?: string[]): string[] => {
9 | let matchRet = null;
10 | const pkgNames: string[] = [];
11 | while ((matchRet = regex.exec(code)) != null) {
12 | const pkgName = matchRet[3];
13 | if (excludePkgs?.indexOf(pkgName) === -1) {
14 | pkgNames.push(pkgName);
15 | }
16 | }
17 | return pkgNames;
18 | };
19 |
20 | const getPkgLatestVersion = (pkg: string): Promise => {
21 | const localConfig = getLocalConfig();
22 | return axios
23 | .get(`${localConfig.npmRegistry}/${pkg}`)
24 | .then((res: any) => {
25 | return [pkg, safeGet(res, 'data.dist-tags.latest', '*')];
26 | })
27 | .catch((err) => {
28 | console.log(`get package ${pkg} info failed, the error is:`, err);
29 | return [pkg, '*'];
30 | });
31 | };
32 |
33 | const analyzeDeps = (
34 | code: string,
35 | excludePkgs?: string[],
36 | ): Promise<{ [pkgName: string]: string }> => {
37 | const pkgs = extractPkgs(code, excludePkgs);
38 | return Promise.all(pkgs.map((pkg) => getPkgLatestVersion(pkg)))
39 | .then((pkgResults) => {
40 | const map: any = {};
41 | pkgResults.forEach(([pkg, version]) => {
42 | map[pkg] = version;
43 | });
44 | return map;
45 | })
46 | .catch((err) => {
47 | console.log('analyze deps failed, the error is:', err);
48 | });
49 | };
50 |
51 | export default analyzeDeps;
52 |
--------------------------------------------------------------------------------
/packages/core/src/utils/flowChartUtils.ts:
--------------------------------------------------------------------------------
1 | import { Cell, Graph } from '@antv/x6';
2 |
3 | export const hasCellSelected = (flowChart: Graph): boolean => {
4 | return flowChart.getSelectedCellCount() > 0;
5 | };
6 |
7 | export const hasNodeSelected = (flowChart: Graph): boolean => {
8 | return (
9 | flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode()).length >
10 | 0
11 | );
12 | };
13 |
14 | export const hasEdgeSelected = (flowChart: Graph): boolean => {
15 | return (
16 | flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge()).length >
17 | 0
18 | );
19 | };
20 |
21 | export const getSelectedNodes = (flowChart: Graph): Cell[] => {
22 | return flowChart.getSelectedCells().filter((cell: Cell) => cell.isNode());
23 | };
24 |
25 | export const getSelectedEdges = (flowChart: Graph): Cell[] => {
26 | return flowChart.getSelectedCells().filter((cell: Cell) => cell.isEdge());
27 | };
28 |
29 | export const toSelectedCellsJSON = (
30 | flowChart: Graph,
31 | ): { cells: Cell.Properties[] } => {
32 | const json = flowChart.toJSON();
33 | const selectedCells = flowChart.getSelectedCells();
34 | return {
35 | cells: json.cells.filter((cell) =>
36 | selectedCells.find((o) => o.id === cell.id),
37 | ),
38 | };
39 | };
40 |
--------------------------------------------------------------------------------
/packages/core/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | import { parse } from 'query-string';
2 |
3 | export const safeParse = (json: string): Record => {
4 | try {
5 | return JSON.parse(json);
6 | } catch (error) {
7 | return {};
8 | }
9 | };
10 |
11 | export const safeGet = (obj: any, keyChain: string, defaultVal?: any): any => {
12 | if (typeof obj !== 'object' || obj === null) {
13 | return defaultVal;
14 | }
15 |
16 | let val = obj;
17 | const keys = keyChain.split('.');
18 | for (const key of keys) {
19 | if (val[key] === undefined) {
20 | return defaultVal;
21 | } else {
22 | val = val[key];
23 | }
24 | }
25 |
26 | return val;
27 | };
28 |
29 | const parseConfig = {
30 | skipNull: true,
31 | skipEmptyString: true,
32 | parseNumbers: false,
33 | parseBooleans: false,
34 | };
35 |
36 | export const parseQuery = (): { [key: string]: any } => {
37 | return parse(location.search, parseConfig);
38 | };
39 |
40 | export const executeScript = (code: string, type = 'module') => {
41 | const script = document.createElement('script');
42 | script.type = type;
43 | script.text = code;
44 | document.body.appendChild(script);
45 | };
46 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "target": "ES2015",
5 | "outDir": "dist/types",
6 | "jsx": "react",
7 | "esModuleInterop": true
8 | },
9 | "include": ["./src"],
10 | "exclude": ["node_modules"]
11 | }
12 |
--------------------------------------------------------------------------------
/packages/plugin-store/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/packages/plugin-store/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/i5ting/imove/0529e8127132380196ae14ea5f505b332dfcf8a6/packages/plugin-store/README.md
--------------------------------------------------------------------------------
/packages/plugin-store/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@imove/plugin-store",
3 | "version": "0.0.3",
4 | "description": "iMove plugin that supports persisting data",
5 | "keywords": [
6 | "imove",
7 | "plugin",
8 | "store"
9 | ],
10 | "author": "smallstonesk <575913857@qq.com>",
11 | "homepage": "https://github.com/suanmei/iMove#readme",
12 | "license": "MIT",
13 | "main": "dist/core.common.js",
14 | "module": "dist/core.esm.js",
15 | "types": "dist/types/index.d.ts",
16 | "directories": {
17 | "dist": "dist"
18 | },
19 | "files": [
20 | "dist"
21 | ],
22 | "publishConfig": {
23 | "access": "public",
24 | "registry": "http://registry.npmjs.org/"
25 | },
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/suanmei/iMove.git"
29 | },
30 | "scripts": {
31 | "prepublishOnly": "node scripts/prepublish.js",
32 | "declare-type": "tsc --emitDeclarationOnly",
33 | "build": "rollup -c & npm run declare-type",
34 | "watch": "watch \"npm run build\" ./src"
35 | },
36 | "bugs": {
37 | "url": "https://github.com/suanmei/iMove/issues"
38 | },
39 | "devDependencies": {},
40 | "dependencies": {}
41 | }
42 |
--------------------------------------------------------------------------------
/packages/plugin-store/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from 'path';
2 | import rollupBaseConfig from '../../rollup.config';
3 | import typescript from 'rollup-plugin-typescript';
4 | import pkg from './package.json';
5 |
6 | export default Object.assign(rollupBaseConfig, {
7 | input: path.join(__dirname, './src/index.ts'),
8 | output: [
9 | {
10 | file: pkg.main,
11 | format: 'cjs',
12 | },
13 | {
14 | file: pkg.module,
15 | format: 'es',
16 | },
17 | ],
18 | plugins: [
19 | typescript(),
20 | ]
21 | });
22 |
--------------------------------------------------------------------------------
/packages/plugin-store/scripts/prepublish.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const path = require('path');
4 | const prePublish = require('../../../scripts/prepublish');
5 |
6 | prePublish('@imove/plugin-store', path.join(__dirname, '../'));
7 |
--------------------------------------------------------------------------------
/packages/plugin-store/src/index.ts:
--------------------------------------------------------------------------------
1 | const storeSymbol = Symbol('store');
2 |
3 | const plugin = (logic: any) => {
4 | return {
5 | ctxCreated(ctx: { [key: string]: any }) {
6 | if (!logic[storeSymbol]) {
7 | logic[storeSymbol] = {};
8 | }
9 | ctx.store = {
10 | set(key: string, val: any) {
11 | logic[storeSymbol][key] = val;
12 | },
13 | get(key: string) {
14 | return logic[storeSymbol][key];
15 | },
16 | remove(key: string) {
17 | delete logic[storeSymbol][key];
18 | },
19 | };
20 | },
21 | };
22 | };
23 |
24 | export default plugin;
25 |
--------------------------------------------------------------------------------
/packages/plugin-store/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig",
3 | "compilerOptions": {
4 | "outDir": "dist/types"
5 | },
6 | "include": ["./src", "./types"]
7 | }
8 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import resolve from '@rollup/plugin-node-resolve';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import strip from '@rollup/plugin-strip';
4 | import json from '@rollup/plugin-json';
5 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot';
6 |
7 | const input = './src/index.tsx';
8 | const extensions = ['.js', '.jsx', '.ts', '.tsx'];
9 |
10 | export default {
11 | input,
12 | plugins: [
13 | resolve({ extensions, preferBuiltins: true }),
14 | commonjs(),
15 | json(),
16 | strip({ debugger: true }),
17 | sizeSnapshot({ printInfo: false }),
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/sandbox.config.json:
--------------------------------------------------------------------------------
1 | {
2 | "template": "node",
3 | "container": {
4 | "port": 8000
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/scripts/prepublish.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const childProcess = require('child_process');
4 | const ora = require('ora');
5 |
6 | const timeout = 1000 * 60 * 2;
7 | const buildCommand = `npm run build`;
8 |
9 | function prePublish(packageAlias, packagePath) {
10 | const spinner = ora(`Building "${packageAlias}" library`).start();
11 |
12 | try {
13 | childProcess.execSync(buildCommand, { timeout, cwd: packagePath });
14 | } catch (error) {
15 | spinner.fail(`Could not finish building "${packageAlias}" library`);
16 | process.exit();
17 | }
18 |
19 | spinner.succeed(`Finished building "${packageAlias}" library`);
20 | }
21 |
22 | module.exports = prePublish;
23 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "esnext",
4 | "module": "commonjs",
5 | "allowJs": false,
6 | "jsx": "react",
7 | "declaration": true,
8 | "types": [],
9 | "strict": true,
10 | "resolveJsonModule": true,
11 | "experimentalDecorators": true,
12 | "skipLibCheck": true
13 | }
14 | }
--------------------------------------------------------------------------------