├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── abc.json
├── lerna.json
├── package.json
├── packages
├── demo-g6
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── build.plugin.js
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── index.ts
│ │ ├── static
│ │ │ ├── assets.json
│ │ │ └── schema.json
│ │ └── tools
│ │ │ └── tool-demo.tsx
│ └── tsconfig.json
├── demo-x6
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── build.plugin.js
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── index.ts
│ │ ├── plugins
│ │ │ ├── delete-node-button.tsx
│ │ │ ├── index.scss
│ │ │ ├── save-schema-button.tsx
│ │ │ ├── undo-rodo-pane.tsx
│ │ │ └── x6-designer-extension.tsx
│ │ └── static
│ │ │ ├── assets.json
│ │ │ └── schema.json
│ └── tsconfig.json
├── g-react-renderer
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ │ ├── common
│ │ │ ├── commonConfig.ts
│ │ │ ├── commonStyle.ts
│ │ │ └── index.tsx
│ │ ├── components
│ │ │ ├── index.tsx
│ │ │ └── textarea
│ │ │ │ └── index.tsx
│ │ ├── diff
│ │ │ ├── index.js
│ │ │ ├── patches.js
│ │ │ ├── render.js
│ │ │ ├── util.js
│ │ │ └── vpatch.js
│ │ ├── elements.ts
│ │ ├── index.ts
│ │ ├── layout
│ │ │ ├── index.js
│ │ │ ├── resolveDimensions.js
│ │ │ ├── resolveStyles.js
│ │ │ └── resolveTextLayout.ts
│ │ ├── locale
│ │ │ ├── en-us.js
│ │ │ └── zh-cn.js
│ │ ├── main.scss
│ │ ├── render
│ │ │ ├── elements
│ │ │ │ ├── renderImage.ts
│ │ │ │ ├── renderNode.ts
│ │ │ │ ├── renderRect.ts
│ │ │ │ ├── renderText.ts
│ │ │ │ └── rendererLink.ts
│ │ │ ├── index.ts
│ │ │ └── operations
│ │ │ │ └── transform.ts
│ │ ├── renderer
│ │ │ ├── g.ts
│ │ │ ├── index.ts
│ │ │ ├── propsEqual.js
│ │ │ └── renderer.js
│ │ ├── scss
│ │ │ └── variables.scss
│ │ └── util
│ │ │ ├── index.ts
│ │ │ ├── measureExactText.ts
│ │ │ └── measureText.ts
│ └── tsconfig.json
├── plugin-core
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ │ └── index.ts
│ └── tsconfig.json
├── plugin-g6-designer
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ │ ├── DesignerView.tsx
│ │ ├── builtin-commands
│ │ │ ├── index.ts
│ │ │ ├── insert-item.ts
│ │ │ ├── remove-item.ts
│ │ │ ├── undo-redo.ts
│ │ │ └── zoom-in-out.ts
│ │ ├── designer.ts
│ │ ├── graph
│ │ │ ├── behaviors
│ │ │ │ ├── click-item.ts
│ │ │ │ ├── double-finger.ts
│ │ │ │ ├── hover-item.ts
│ │ │ │ └── index.ts
│ │ │ ├── initGraph.tsx
│ │ │ ├── interfaces.ts
│ │ │ ├── layouts
│ │ │ │ ├── index.ts
│ │ │ │ └── layout
│ │ │ │ │ ├── customLayout.js
│ │ │ │ │ ├── customTreeGrid.js
│ │ │ │ │ └── indented.ts
│ │ │ ├── shapes
│ │ │ │ └── default-shape.ts
│ │ │ └── utils.tsx
│ │ ├── index.ts
│ │ └── items
│ │ │ ├── edge
│ │ │ └── index.tsx
│ │ │ ├── index.less
│ │ │ ├── index.tsx
│ │ │ ├── node
│ │ │ └── index.tsx
│ │ │ └── state.ts
│ └── tsconfig.json
├── plugin-materials-pane
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ │ ├── icon.tsx
│ │ ├── index.tsx
│ │ └── pane
│ │ │ ├── Icon
│ │ │ ├── Component.tsx
│ │ │ ├── component.svg
│ │ │ ├── icon.svg
│ │ │ ├── index.module.scss
│ │ │ └── index.tsx
│ │ │ ├── components
│ │ │ ├── Category
│ │ │ │ ├── index.module.scss
│ │ │ │ └── index.tsx
│ │ │ ├── Component
│ │ │ │ ├── index.module.scss
│ │ │ │ └── index.tsx
│ │ │ ├── DragGhost
│ │ │ │ ├── index.scss
│ │ │ │ └── index.tsx
│ │ │ ├── List
│ │ │ │ ├── index.module.scss
│ │ │ │ └── index.tsx
│ │ │ └── Tab
│ │ │ │ ├── index.module.scss
│ │ │ │ └── index.tsx
│ │ │ ├── index.module.scss
│ │ │ ├── index.tsx
│ │ │ ├── store
│ │ │ └── index.ts
│ │ │ └── utils
│ │ │ └── transform.ts
│ └── tsconfig.json
├── plugin-tools
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ │ ├── common
│ │ │ ├── command.ts
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ └── utils.ts
│ │ ├── index.scss
│ │ ├── index.ts
│ │ ├── plugins
│ │ │ ├── InsertItemPlugin.tsx
│ │ │ ├── OperateButtonPlugin.tsx
│ │ │ ├── RemoveItemPlugin.tsx
│ │ │ ├── UndoRedoPlugin.tsx
│ │ │ ├── ZoomPlugin.tsx
│ │ │ └── index.ts
│ │ └── tools
│ │ │ ├── insertItem.tsx
│ │ │ ├── logo.tsx
│ │ │ ├── removeItem.tsx
│ │ │ ├── save.tsx
│ │ │ ├── zoomIn.tsx
│ │ │ └── zoomOut.tsx
│ └── tsconfig.json
└── plugin-x6-designer
│ ├── CHANGELOG.md
│ ├── README.md
│ ├── build.json
│ ├── package.json
│ ├── src
│ ├── DesignerView.tsx
│ ├── builtin-commands
│ │ ├── index.ts
│ │ ├── remove-item.ts
│ │ ├── undo-redo.ts
│ │ └── zoom-in-out.ts
│ ├── designer.ts
│ ├── graph
│ │ ├── initEvents.ts
│ │ ├── initGraph.tsx
│ │ ├── register
│ │ │ └── shape
│ │ │ │ ├── const.ts
│ │ │ │ ├── lce-circle.ts
│ │ │ │ ├── lce-diamond.ts
│ │ │ │ └── lce-rect.ts
│ │ ├── registerShape.ts
│ │ └── util.ts
│ ├── index.tsx
│ └── items
│ │ ├── edge
│ │ └── index.tsx
│ │ ├── index.less
│ │ ├── index.tsx
│ │ ├── node
│ │ └── index.tsx
│ │ ├── state.ts
│ │ └── utils.ts
│ └── tsconfig.json
├── scripts
├── create.sh
├── publish.sh
├── setup.js
├── setup.sh
└── start.sh
├── server
└── index.js
├── templates
├── .babelrc
├── README.md
├── _tsconfig.json
├── build.json
├── package.json
└── src
│ └── index.ts
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by https://www.gitignore.io/api/node
3 |
4 | ### Node ###
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 | yarn.lock
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (http://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 |
43 | jspm_packages/
44 |
45 | # Typescript v1 declaration files
46 | # typings/
47 |
48 | # Optional npm cache directory
49 | .npm
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Optional REPL history
55 | .node_repl_history
56 |
57 | # Output of 'npm pack'
58 | *.tgz
59 |
60 | # Yarn Integrity file
61 | .yarn-integrity
62 |
63 | # dotenv environment variables file
64 | .env
65 |
66 | .idea
67 | # End of https://www.gitignore.io/api/node
68 |
69 | lib/
70 | dist/
71 | es/
72 | build/
73 |
74 | .DS_Store
75 | .cache
76 |
77 | package-lock.json
78 | git_log.sh
79 | node_modules/
80 |
81 | stories/Test
82 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Alibaba
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.md:
--------------------------------------------------------------------------------
1 | ## Graph Editor Framework
2 | 简介:基于阿里巴巴低代码引擎的统一图框架(图编排,图分析等等)
3 |
4 | ---
5 |
6 | ### 框架依赖
7 | LCE:[lowcode engine]('https://lowcode-engine.cn/index')
8 |
9 | G6: [G6 图可视化引擎]('https://g6.antv.vision/zh')
10 |
11 | X6: [X6 图编辑引擎]('https://x6.antv.vision/zh')
12 |
13 |
14 | ### 开始调试
15 | #### 本地调试
16 | `npm install`
17 |
18 | `npm run setup`
19 |
20 | `npm run start:x6`
21 |
22 | ### Demo 参考
23 | https://github.com/alibaba/lowcode-demo/tree/main/demo-graph-x6
24 |
--------------------------------------------------------------------------------
/abc.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lce-graph",
3 | "assets":{
4 | "type":"command",
5 | "command":{
6 | "cmd": [
7 | "sh ./scripts/publish.sh $BUILD_DEST"
8 | ]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "lerna": "4.0.0",
3 | "version": "1.0.12",
4 | "npmClient": "yarn",
5 | "useWorkspaces": true,
6 | "packages": [
7 | "packages/*"
8 | ],
9 | "command": {
10 | "bootstrap": {
11 | "npmClientArgs": [
12 | "--no-package-lock"
13 | ]
14 | },
15 | "version": {
16 | "allowBranch": [
17 | "master",
18 | "main",
19 | "release/*",
20 | "daily/*",
21 | "refactor/*"
22 | ]
23 | },
24 | "publish": {
25 | "npmClient": "npm",
26 | "verifyRegistry": false,
27 | "verifyAccess": false,
28 | "ignoreChanges": [
29 | "**/*.md",
30 | "**/test/**"
31 | ],
32 | "message": "chore(release): publish %v",
33 | "conventionalCommits": true
34 | }
35 | }
36 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/graph-editor",
3 | "private": true,
4 | "workspaces": [
5 | "packages/*"
6 | ],
7 | "scripts": {
8 | "start:x6": "lerna exec --scope @alilc/lce-graph-demo-x6 -- npm run start",
9 | "start:g6": "lerna exec --scope @alilc/lce-graph-demo-g6 -- npm run start",
10 | "start": "./scripts/start.sh",
11 | "setup": "node ./scripts/setup.js",
12 | "build": "lerna run build --stream",
13 | "clean": "rm -rf node_modules && lerna clean -y",
14 | "create": "./scripts/create.sh",
15 | "lint": "npm-run-all --parallel tslint stylelint",
16 | "tslint": "eslint --cache --ext .ts,.tsx,.js,.jsx ./",
17 | "stylelint": "stylelint ./**/*.scss",
18 | "prettier": "prettier -c --write '**/*'",
19 | "pretty-quick": "pretty-quick --staged",
20 | "test": "jest",
21 | "test-live": "jest --watch",
22 | "coverage": "jest --coverage",
23 | "publish": "lerna publish patch --force-publish --exact --no-changelog",
24 | "publish:prerelease": "lerna publish prerelease --no-changelog"
25 | },
26 | "devDependencies": {
27 | "@alib/build-scripts": "^0.1.18",
28 | "@alifd/build-plugin-lowcode": "^0.3.8",
29 | "@types/react-router": "5.1.18",
30 | "babel-jest": "^26.5.2",
31 | "build-plugin-component": "^1.12.0",
32 | "del": "^6.1.1",
33 | "execa": "^5.1.1",
34 | "f2elint": "^2.0.1",
35 | "gulp": "^4.0.2",
36 | "husky": "^7.0.4",
37 | "lerna": "^4.0.0",
38 | "rimraf": "^3.0.2",
39 | "typescript": "4.6.2",
40 | "yarn": "^1.22.17"
41 | },
42 | "engines": {
43 | "node": ">=14.17.0 <18"
44 | },
45 | "tnpm": {
46 | "mode": "yarn",
47 | "lockfile": "enable"
48 | },
49 | "publishConfig": {
50 | "registry": "https://registry.antfin-inc.com"
51 | }
52 | }
--------------------------------------------------------------------------------
/packages/demo-g6/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file.
4 | See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5 |
6 | ## [1.0.3-alpha.7](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.3-alpha.6...v1.0.3-alpha.7) (2023-02-08)
7 |
8 |
9 | ### Bug Fixes
10 |
11 | * 修复由于 exportName 不正确导致的无法显示配置的问题 ([9cafbe8](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/9cafbe80d47ce08e4919ea929c63be8979ed1ff9))
12 |
13 |
14 | ### Features
15 |
16 | * 调整用外部资源 ([49e4f53](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/49e4f537901b243d8f14af6ad2e606a0d95e7fe6))
17 | * 调整自定义command方式 ([aa714a3](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/aa714a382a14f8723d910f6a74d17e2ca66735c0))
18 | * 调整g6的commanManager及自定义command实现 ([146516e](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/146516e4b2eb03f38f6aab9a2217e00b6d8c343b))
19 | * 关闭配置节点、边、行为的配置 ([9626b2f](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/9626b2fbbdb70f6df3d13c5745c9cf00df2af6a0))
20 | * 统一g6 x6两边designer ([585a0e0](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/585a0e055224ce097a90727585f71fbbf3e5d90c))
21 |
22 |
23 |
24 |
25 |
26 | ## [1.0.3-alpha.5](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.3-alpha.4...v1.0.3-alpha.5) (2023-01-31)
27 |
28 | **Note:** Version bump only for package @alilc/lce-graph-demo-g6
29 |
30 |
31 |
32 |
33 |
34 | ## [1.0.3-alpha.3](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.3-alpha.2...v1.0.3-alpha.3) (2023-01-31)
35 |
36 | **Note:** Version bump only for package @alilc/lce-graph-demo-g6
37 |
38 |
39 |
40 |
41 |
42 | ## [1.0.3-alpha.2](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.3-alpha.1...v1.0.3-alpha.2) (2023-01-31)
43 |
44 | **Note:** Version bump only for package @ali/lce-graph-demo-g6
45 |
46 |
47 |
48 |
49 |
50 | ## [1.0.1-alpha.13](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.1-alpha.12...v1.0.1-alpha.13) (2022-09-27)
51 |
52 |
53 | ### Features
54 |
55 | * 支持动态changeData ([976e7da](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/976e7daf3369e9b297ba10bd9e38f9bb1b567795))
56 |
57 |
58 |
59 |
60 |
61 | ## [1.0.1-alpha.12](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.1-alpha.11...v1.0.1-alpha.12) (2022-09-21)
62 |
63 |
64 | ### Features
65 |
66 | * 从物料里读取边配置 ([5df686d](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/5df686de62b5d4e6fa173c04c3524f8f5d9a7bcb))
67 | * 删除common独立包,整合入tools ([4c0107b](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/4c0107b15e6754ad2efe2b5363aeb19bed1d8d38))
68 |
69 |
70 |
71 |
72 |
73 | ## [1.0.1-alpha.2](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/compare/v1.0.1-alpha.0...v1.0.1-alpha.2) (2022-09-15)
74 |
75 | **Note:** Version bump only for package @ali/lce-graph-demo-g6
76 |
77 |
78 |
79 |
80 |
81 | ## 1.0.1-alpha.0 (2022-09-15)
82 |
83 |
84 | ### Features
85 |
86 | * 剥离bootstrap依赖 ([e6f99c3](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/e6f99c3e885cec7d1d600dbfcc106c993169943f))
87 | * 导出g-canvas-renderer,临时关闭fontSize设置 ([980afbf](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/980afbf173440decbed8153d039b8c6787ea46d0))
88 | * 统一g6/x6的init方式 ([de04c89](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/de04c8922ac6f66e77ece1cf42c93cb08dcbe547))
89 | * demo跑通,无须build ([441bb7a](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/441bb7aa2f7e377fc64fc3f0b653e0c05b9400be))
90 | * x6剥离bootstrap依赖 ([ccd8230](https://gitlab.alibaba-inc.com/graph-editor-engine/graph-editor/commit/ccd8230525ae1f7997a0784e1a79ee37f4b3ad86))
91 |
--------------------------------------------------------------------------------
/packages/demo-g6/README.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ---
3 |
--------------------------------------------------------------------------------
/packages/demo-g6/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "entry": {
3 | "index": "./src/index.ts"
4 | },
5 | "publicPath": "/",
6 | "vendor": false,
7 | "externals": {
8 | "react": "var window.React",
9 | "react-dom": "var window.ReactDOM",
10 | "prop-types": "var window.PropTypes",
11 | "@antv/g6": "var window.G6",
12 | "@alifd/next": "var window.Next",
13 | "@alilc/lowcode-engine": "var window.AliLowCodeEngine",
14 | "@alilc/lowcode-editor-core": "var window.AliLowCodeEngine.common.editorCabin",
15 | "@alilc/lowcode-editor-skeleton": "var window.AliLowCodeEngine.common.skeletonCabin",
16 | "@alilc/lowcode-designer": "var window.AliLowCodeEngine.common.designerCabin",
17 | "@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt",
18 | "moment": "var window.moment",
19 | "lodash": "var window._"
20 | },
21 | "devServer": {
22 | "liveReload": false,
23 | "hot": false
24 | },
25 | "polyfill": false,
26 | "browserslist": "chrome >= 60",
27 | "plugins": [
28 | "build-plugin-react-app",
29 | [
30 | "build-plugin-fusion",
31 | {
32 | "externalNext": true
33 | }
34 | ],
35 | [
36 | "build-plugin-moment-locales",
37 | {
38 | "locales": [
39 | "zh-cn",
40 | "en-us"
41 | ]
42 | }
43 | ],
44 | "./build.plugin.js"
45 | ]
46 | }
--------------------------------------------------------------------------------
/packages/demo-g6/build.plugin.js:
--------------------------------------------------------------------------------
1 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
2 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
3 | // const BundleBuddyWebpackPlugin = require("bundle-buddy-webpack-plugin");
4 | const webpack = require('webpack');
5 | const Config = require('webpack-chain');
6 | const packageInfo = require('./package.json');
7 |
8 | module.exports = ({ context, onGetWebpackConfig }) => {
9 | onGetWebpackConfig((config) => {
10 | config.resolve.extensions.add('.less').end();
11 | config.plugin('DefinePlugin').use(new webpack.DefinePlugin({
12 | VERSION: JSON.stringify(packageInfo.version)
13 | }));
14 | config.resolve
15 | .plugin('tsconfigpaths')
16 | .use(TsconfigPathsPlugin, [{
17 | configFile: './tsconfig.json',
18 | }]);
19 | // config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin);
20 | // config.devtool('source-map');
21 | // config.plugin('BundleBuddyWebpackPlugin').use(new BundleBuddyWebpackPlugin());
22 | // if (context.command === 'start') {
23 | // config.devtool('inline-source-map');
24 | // }
25 | });
26 | };
--------------------------------------------------------------------------------
/packages/demo-g6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-demo-g6",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "private": true,
8 | "files": [
9 | "lib",
10 | "es"
11 | ],
12 | "scripts": {
13 | "start": "build-scripts start",
14 | "test": "jest -c jest.config.js"
15 | },
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public",
19 | "registry": "https://registry.npmjs.org/"
20 | },
21 | "dependencies": {
22 | "@alilc/lowcode-engine": "^1"
23 | },
24 | "devDependencies": {
25 | "@alib/build-scripts": "^0.1.18",
26 | "build-plugin-component": "^1.10.0",
27 | "build-plugin-fusion": "^0.1.0",
28 | "build-plugin-moment-locales": "^0.1.0",
29 | "build-plugin-react-app": "^1.7.13",
30 | "tsconfig-paths-webpack-plugin": "^3.3.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/demo-g6/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 设计器
7 |
8 |
9 |
10 |
11 |
12 |
22 |
25 |
26 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
58 |
59 |
60 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/packages/demo-g6/src/index.ts:
--------------------------------------------------------------------------------
1 | import { plugins, init as _init } from '@alilc/lowcode-engine';
2 | import PluginG6Designer from '@alilc/lce-graph-g6-designer';
3 | import {
4 | logo,
5 | InsertItemPlugin,
6 | OperateButtonPlugin,
7 | ZoomPlugin,
8 | RemoveItemPlugin,
9 | UndoRedoPlugin
10 | } from '@alilc/lce-graph-tools';
11 | import ToolDemo from './tools/tool-demo';
12 | import PluginCore from '@alilc/lce-graph-core';
13 | import assets from './static/assets.json';
14 | import schema from './static/schema.json';
15 |
16 | (async function main() {
17 | // 新增节点数据模型
18 | const newNodeModel = {
19 | componentName: 'DemoNode',
20 | props: {
21 | collapsed: false,
22 | type: 'default-shape',
23 | itemColor: 'default',
24 | },
25 | }
26 |
27 | // 核心插件
28 | await plugins.register(PluginCore, {
29 | assets,
30 | schema
31 | });
32 |
33 | await plugins.register(PluginG6Designer, {
34 | type: 'tree',
35 | behaviors: [],
36 | graphConfig: [],
37 | layoutConfig: {
38 | compactBox: {
39 | direction: 'TB',
40 | }
41 | }
42 | });
43 |
44 | // tools
45 | await plugins.register(logo);
46 | await plugins.register(ZoomPlugin);
47 | await plugins.register(RemoveItemPlugin);
48 | await plugins.register(OperateButtonPlugin, [
49 | {
50 | callback: (schema: any) => {
51 | console.log('保存回调', schema)
52 | },
53 | name: 'save',
54 | text: '保存',
55 | hotkey: 'command+s',
56 | },
57 | {
58 | callback: (schema: any) => {
59 | console.log('发布回调', schema)
60 | },
61 | name: 'publish',
62 | text: '发布',
63 | hotkey: 'command+p',
64 | },
65 | {
66 | callback: (schema: any) => {
67 | console.log('自定义操作按钮回调', schema)
68 | },
69 | name: 'test',
70 | text: '测试自定义按钮',
71 | hotkey: 'command+0',
72 | btnProps: { type: 'secondary' },
73 | },
74 | ]);
75 |
76 | await plugins.register(InsertItemPlugin, {
77 | model: newNodeModel
78 | });
79 | await plugins.register(UndoRedoPlugin);
80 | // 自定义插件
81 | await plugins.register(ToolDemo);
82 |
83 | _init();
84 | })();
85 |
--------------------------------------------------------------------------------
/packages/demo-g6/src/static/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | {
4 | "package": "@alilc/lce-g6-items",
5 | "version": "0.1.5",
6 | "library": "BizComps",
7 | "urls": [
8 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/render/default/view.js",
9 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/render/default/view.css"
10 | ],
11 | "editUrls": [
12 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/view.js",
13 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/view.css"
14 | ],
15 | "advancedUrls": {
16 | "default": [
17 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/render/default/view.js",
18 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/render/default/view.css"
19 | ]
20 | },
21 | "advancedEditUrls": {}
22 | }
23 | ],
24 | "components": [
25 | {
26 | "exportName": "AlilcLceG6ItemsMeta",
27 | "npm": {
28 | "package": "@alilc/lce-g6-items",
29 | "version": "0.1.5"
30 | },
31 | "url": "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/meta.js",
32 | "urls": {
33 | "default": "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/meta.js"
34 | },
35 | "advancedUrls": {
36 | "default": [
37 | "https://uipaas-assets.com/prod/npm/@alilc/lce-g6-items/1.0.4/build/lowcode/meta.js"
38 | ]
39 | }
40 | }
41 | ],
42 | "sort": {
43 | "groupList": [
44 | "精选组件",
45 | "原子组件"
46 | ],
47 | "categoryList": [
48 | "基础元素",
49 | "布局容器类",
50 | "表格类",
51 | "表单详情类",
52 | "帮助类",
53 | "对话框类",
54 | "业务类",
55 | "通用",
56 | "引导",
57 | "信息输入",
58 | "信息展示",
59 | "信息反馈"
60 | ]
61 | },
62 | "groupList": [
63 | "精选组件",
64 | "原子组件"
65 | ],
66 | "ignoreComponents": {}
67 | }
--------------------------------------------------------------------------------
/packages/demo-g6/src/static/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "componentName": "Page",
3 | "children": [
4 | {
5 | "id": "1",
6 | "componentName": "DemoNode",
7 | "props": {
8 | "hidden": true,
9 | "collapsed": false,
10 | "itemColor": "default",
11 | "itemName": "节点标题",
12 | "type": "default-shape"
13 | },
14 | "children": [
15 | {
16 | "id": "1.1",
17 | "componentName": "DemoNode",
18 | "props": {
19 | "hidden": true,
20 | "collapsed": false,
21 | "itemColor": "default",
22 | "itemName": "节点标题1.1",
23 | "type": "default-shape"
24 | }
25 | },
26 | {
27 | "id": "1.2",
28 | "componentName": "DemoNode",
29 | "props": {
30 | "hidden": true,
31 | "collapsed": false,
32 | "itemColor": "default",
33 | "itemName": "节点标题1.2",
34 | "type": "default-shape"
35 | }
36 | }
37 | ]
38 | }
39 | ]
40 | }
--------------------------------------------------------------------------------
/packages/demo-g6/src/tools/tool-demo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
3 | import { G6Designer } from '@alilc/lce-graph-g6-designer';
4 | class Tool extends React.Component<{ ctx: ILowCodePluginContext }, any> {
5 | handleClick() {
6 | const { ctx } = this.props;
7 | // 触发自定义命令
8 | ctx.event.emit('toolDemoEvent', '我是自定义工具信息');
9 | }
10 | render() {
11 | return (
12 |
13 | 测试自定义工具
14 |
15 | )
16 | }
17 | }
18 |
19 | const ToolDemo = (ctx: ILowCodePluginContext) => {
20 | return {
21 | name: 'toolDemo',
22 | async init() {
23 | const { skeleton, plugins } = ctx;
24 | const g6Designer = plugins['plugin-g6-designer'] as G6Designer;
25 | // 实现命令回调
26 | g6Designer.registerCommand('toolDemoEvent', (ctx, graph, data) => { alert(data) });
27 | skeleton.add({
28 | name: 'toolDemo',
29 | area: 'toolbar',
30 | type: 'Widget',
31 | props: {
32 | align: 'left'
33 | },
34 | content: Tool,
35 | contentProps: {
36 | ctx
37 | },
38 | });
39 | },
40 | };
41 | };
42 | ToolDemo.pluginName = 'toolDemo';
43 |
44 | export default ToolDemo;
--------------------------------------------------------------------------------
/packages/demo-g6/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
--------------------------------------------------------------------------------
/packages/demo-x6/README.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ---
3 |
--------------------------------------------------------------------------------
/packages/demo-x6/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "entry": {
3 | "index": "./src/index.ts"
4 | },
5 | "publicPath": "/",
6 | "vendor": false,
7 | "externals": {
8 | "react": "var window.React",
9 | "react-dom": "var window.ReactDOM",
10 | "prop-types": "var window.PropTypes",
11 | "@antv/x6": "var window.X6",
12 | "@alifd/next": "var window.Next",
13 | "@alilc/lowcode-engine": "var window.AliLowCodeEngine",
14 | "@alilc/lowcode-editor-core": "var window.AliLowCodeEngine.common.editorCabin",
15 | "@alilc/lowcode-editor-skeleton": "var window.AliLowCodeEngine.common.skeletonCabin",
16 | "@alilc/lowcode-designer": "var window.AliLowCodeEngine.common.designerCabin",
17 | "@alilc/lowcode-engine-ext": "var window.AliLowCodeEngineExt",
18 | "moment": "var window.moment",
19 | "lodash": "var window._"
20 | },
21 | "devServer": {
22 | "liveReload": false,
23 | "hot": false
24 | },
25 | "polyfill": false,
26 | "browserslist": "chrome >= 60",
27 | "plugins": [
28 | "build-plugin-react-app",
29 | [
30 | "build-plugin-fusion",
31 | {
32 | "externalNext": true
33 | }
34 | ],
35 | [
36 | "build-plugin-moment-locales",
37 | {
38 | "locales": [
39 | "zh-cn",
40 | "en-us"
41 | ]
42 | }
43 | ],
44 | "./build.plugin.js"
45 | ]
46 | }
--------------------------------------------------------------------------------
/packages/demo-x6/build.plugin.js:
--------------------------------------------------------------------------------
1 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
2 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
3 | // const BundleBuddyWebpackPlugin = require("bundle-buddy-webpack-plugin");
4 | const webpack = require('webpack');
5 | const Config = require('webpack-chain');
6 | const packageInfo = require('./package.json');
7 |
8 | module.exports = ({ context, onGetWebpackConfig }) => {
9 | onGetWebpackConfig((config) => {
10 | config.resolve.extensions.add('.less').end();
11 | config.plugin('DefinePlugin').use(new webpack.DefinePlugin({
12 | VERSION: JSON.stringify(packageInfo.version)
13 | }));
14 | config.resolve
15 | .plugin('tsconfigpaths')
16 | .use(TsconfigPathsPlugin, [{
17 | configFile: './tsconfig.json',
18 | }]);
19 | // config.plugin('webpack-bundle-analyzer').use(BundleAnalyzerPlugin);
20 | // config.devtool('source-map');
21 | // config.plugin('BundleBuddyWebpackPlugin').use(new BundleBuddyWebpackPlugin());
22 | // if (context.command === 'start') {
23 | // config.devtool('inline-source-map');
24 | // }
25 | });
26 | };
27 |
--------------------------------------------------------------------------------
/packages/demo-x6/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-demo-x6",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "private": true,
8 | "files": [
9 | "lib",
10 | "es"
11 | ],
12 | "scripts": {
13 | "start": "build-scripts start",
14 | "test": "jest -c jest.config.js"
15 | },
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public",
19 | "registry": "https://registry.npmjs.org/"
20 | },
21 | "dependencies": {
22 | "@alilc/lowcode-engine": "^1"
23 | },
24 | "devDependencies": {
25 | "@alib/build-scripts": "^0.1.18",
26 | "build-plugin-component": "^1.10.0",
27 | "build-plugin-fusion": "^0.1.0",
28 | "build-plugin-moment-locales": "^0.1.0",
29 | "build-plugin-react-app": "^1.7.13",
30 | "tsconfig-paths-webpack-plugin": "^3.3.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/demo-x6/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 阿里低代码引擎 Demo
9 |
10 |
11 |
12 |
13 |
14 |
15 |
17 |
18 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
42 |
43 |
44 |
45 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/packages/demo-x6/src/index.ts:
--------------------------------------------------------------------------------
1 | import { plugins, init, project } from '@alilc/lowcode-engine';
2 | import PluginX6Designer from '@alilc/lce-graph-x6-designer';
3 | import PluginMaterialsPane from '@alilc/lce-graph-materials-pane';
4 | import PluginCore from '@alilc/lce-graph-core';
5 | import { RemoveItemPlugin, OperateButtonPlugin, UndoRedoPlugin, ZoomPlugin, logo } from '@alilc/lce-graph-tools';
6 | import PluginX6DesignerExtension from './plugins/x6-designer-extension';
7 | import assets from './static/assets.json';
8 | import schema from './static/schema.json';
9 |
10 | (async function main() {
11 | await plugins.register(PluginCore, {
12 | assets,
13 | schema
14 | });
15 | await plugins.register(PluginX6Designer);
16 | await plugins.register(PluginMaterialsPane);
17 | await plugins.register(PluginX6DesignerExtension);
18 | await plugins.register(logo);
19 | await plugins.register(RemoveItemPlugin);
20 | await plugins.register(OperateButtonPlugin, [
21 | {
22 | callback: (schema: any) => {
23 | console.log('保存回调', schema)
24 | },
25 | name: 'save',
26 | text: '保存',
27 | hotkey: 'command+s',
28 | },
29 | {
30 | callback: (schema: any) => {
31 | console.log('发布回调', schema)
32 | },
33 | name: 'publish',
34 | text: '发布',
35 | hotkey: 'command+p',
36 | },
37 | {
38 | callback: (schema: any) => {
39 | console.log('自定义操作按钮回调', schema)
40 | },
41 | name: 'test',
42 | text: '测试自定义按钮',
43 | hotkey: 'command+0',
44 | btnProps: { type: 'secondary' },
45 | },
46 | ]);
47 | await plugins.register(UndoRedoPlugin);
48 | await plugins.register(ZoomPlugin);
49 | init();
50 | })();
51 |
--------------------------------------------------------------------------------
/packages/demo-x6/src/plugins/delete-node-button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { hotkey, project, Node } from '@alilc/lowcode-engine';
3 | import { isFormEvent } from '@alilc/lowcode-utils';
4 | import { IDesigner } from '@alilc/lce-graph-x6-designer';
5 |
6 | interface IProps {
7 | x6Designer: IDesigner
8 | }
9 |
10 | export default class DeleteNode extends React.Component {
11 | onDelete = (e: any) => {
12 | if (isFormEvent(e)) return;
13 | const selectedNodes = project.currentDocument?.selection.getNodes();
14 | if (selectedNodes?.length) {
15 | selectedNodes.forEach(node => {
16 | // 可被删除判断
17 | if (node?.componentMeta?.getMetadata().tags?.includes('node')) {
18 | const allNodes = Array.from(project.currentDocument?.nodesMap.values() || []);
19 | // 相关线
20 | const lines = allNodes.filter(n => (n?.componentMeta?.getMetadata().tags?.includes('edge') && (n.getPropValue('source') === node.id || n.getPropValue('target') === node.id)));
21 | lines.forEach(line => {
22 | project.currentDocument?.removeNode(line.id);
23 | });
24 | }
25 | project.currentDocument?.removeNode(node.id);
26 | });
27 | }
28 | }
29 |
30 | componentDidMount() {
31 | // 覆盖原有删除
32 | hotkey.bind(['backspace', 'del'], (e) => {
33 | this.onDelete(e);
34 | });
35 | }
36 |
37 | render() {
38 | return 删除
39 | }
40 | }
--------------------------------------------------------------------------------
/packages/demo-x6/src/plugins/index.scss:
--------------------------------------------------------------------------------
1 | .undo-redo-pane {
2 | display: flex;
3 | margin-left: 12px;
4 | justify-content: space-between;
5 | width: 60px;
6 | }
--------------------------------------------------------------------------------
/packages/demo-x6/src/plugins/save-schema-button.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@alifd/next';
3 | import { project } from '@alilc/lowcode-engine';
4 |
5 | export default class SaveSchema extends React.Component {
6 | onClick = () => {
7 | const schema = project.currentDocument!.exportSchema();
8 | console.log(schema);
9 | }
10 |
11 | render() {
12 | return
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/demo-x6/src/plugins/undo-rodo-pane.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Button } from '@alifd/next';
3 | import { project } from '@alilc/lowcode-engine';
4 |
5 | interface IProps {
6 | }
7 |
8 | export default class UndoRedoPane extends React.Component {
9 | onUndo = () => {
10 | console.log(this.props);
11 | project.currentDocument?.history.back();
12 | }
13 |
14 | onRedo = () => {
15 | project.currentDocument?.history.forward();
16 | }
17 |
18 | render() {
19 | return (
20 |
24 | )
25 | }
26 | }
--------------------------------------------------------------------------------
/packages/demo-x6/src/plugins/x6-designer-extension.tsx:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext, skeleton } from '@alilc/lowcode-engine';
2 | import { IDesigner } from '@alilc/lce-graph-x6-designer';
3 | import * as React from 'react';
4 | import { Graph, Markup } from '@antv/x6';
5 | import ReactDOM from 'react-dom';
6 | import './index.scss';
7 |
8 | /**
9 | * X6 Designer 业务自定义扩展插件
10 | */
11 | function pluginX6DesignerExtension(ctx: ILowCodePluginContext) {
12 | return {
13 | init() {
14 | // 获取 x6 designer 内置插件的导出 api
15 | const x6Designer = ctx.plugins['plugin-x6-designer'] as IDesigner;
16 |
17 | x6Designer.onNodeRender((model, node) => {
18 | // @ts-ignore
19 | // 自定义 node 渲染逻辑
20 | const { name, title } = model.propsData;
21 | node.attr('text/textWrap/text', title || name);
22 | });
23 |
24 | x6Designer.onEdgeRender((model, edge) => {
25 | // @ts-ignore
26 | const { source, target, sourcePortId, targetPortId } = model.propsData;
27 | console.log(sourcePortId, targetPortId);
28 | requestAnimationFrame(() => {
29 | edge.setSource({ cell: source, port: sourcePortId });
30 | edge.setTarget({ cell: target, port: targetPortId });
31 | });
32 |
33 | // https://x6.antv.vision/zh/docs/tutorial/intermediate/edge-labels x6 标签模块
34 | // appendLabel 会触发 onEdgeLabelRender
35 | edge.appendLabel({
36 | markup: Markup.getForeignObjectMarkup(),
37 | attrs: {
38 | fo: {
39 | width: 120,
40 | height: 30,
41 | x: -60,
42 | y: -15,
43 | },
44 | },
45 | });
46 | });
47 |
48 | x6Designer.onEdgeLabelRender((args) => {
49 | const { selectors } = args
50 | const content = selectors.foContent as HTMLDivElement
51 | if (content) {
52 | ReactDOM.render(自定义 react 标签
, content)
53 | }
54 | })
55 | }
56 | }
57 | }
58 |
59 | pluginX6DesignerExtension.pluginName = 'plugin-x6-designer-extension';
60 |
61 | export default pluginX6DesignerExtension;
--------------------------------------------------------------------------------
/packages/demo-x6/src/static/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "packages": [
3 | {
4 | "package": "@alilc/lce-x6-items",
5 | "version": "1.0.7",
6 | "library": "AlilcLceX6Items",
7 | "urls": [
8 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/render/default/view.js",
9 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/render/default/view.css"
10 | ],
11 | "editUrls": [
12 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/view.js",
13 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/view.css"
14 | ],
15 | "advancedUrls": {
16 | "default": [
17 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/render/default/view.js",
18 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/render/default/view.css"
19 | ]
20 | },
21 | "advancedEditUrls": {}
22 | }
23 | ],
24 | "components": [
25 | {
26 | "exportName": "AlilcLceX6ItemsMeta",
27 | "npm": {
28 | "package": "@alilc/lce-x6-items",
29 | "version": "1.0.7"
30 | },
31 | "url": "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/meta.js",
32 | "urls": {
33 | "default": "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/meta.js"
34 | },
35 | "advancedUrls": {
36 | "default": [
37 | "https://uipaas-assets.com/prod/npm/@alilc/lce-x6-items/1.0.7/build/lowcode/meta.js"
38 | ]
39 | }
40 | }
41 | ],
42 | "sort": {
43 | "groupList": [
44 | "精选组件",
45 | "原子组件"
46 | ],
47 | "categoryList": [
48 | "基础元素",
49 | "布局容器类",
50 | "表格类",
51 | "表单详情类",
52 | "帮助类",
53 | "对话框类",
54 | "业务类",
55 | "通用",
56 | "引导",
57 | "信息输入",
58 | "信息展示",
59 | "信息反馈"
60 | ]
61 | },
62 | "groupList": [
63 | "精选组件",
64 | "原子组件"
65 | ],
66 | "ignoreComponents": {}
67 | }
--------------------------------------------------------------------------------
/packages/demo-x6/src/static/schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "componentName": "Page",
3 | "id": "node_dockcviv8fo1",
4 | "fileName": "",
5 | "props": {},
6 | "children": [
7 | {
8 | "id": "node-start",
9 | "componentName": "StartEnd",
10 | "title": "开始节点",
11 | "props": {
12 | "name": "开始节点",
13 | "position": {
14 | "x": 82,
15 | "y": 71
16 | }
17 | }
18 | },
19 | {
20 | "id": "node_ockn610kx9m",
21 | "componentName": "SendEmail",
22 | "title": "发送邮件",
23 | "props": {
24 | "name": "发送邮件",
25 | "position": {
26 | "x": 84,
27 | "y": 263
28 | }
29 | }
30 | },
31 | {
32 | "id": "node-end",
33 | "componentName": "StartEnd",
34 | "title": "结束节点",
35 | "props": {
36 | "name": "结束节点",
37 | "position": {
38 | "x": 82,
39 | "y": 455
40 | }
41 | }
42 | },
43 | {
44 | "id": "node_ockn610kx9b",
45 | "componentName": "Line",
46 | "title": "线",
47 | "props": {
48 | "name": "线",
49 | "source": "node-start",
50 | "target": "node_ockn610kx9m",
51 | "sourcePortId": "b",
52 | "targetPortId": "t"
53 | }
54 | },
55 | {
56 | "id": "node_ockn610kx9a",
57 | "componentName": "Line",
58 | "title": "线",
59 | "props": {
60 | "name": "线",
61 | "source": "node_ockn610kx9m",
62 | "target": "node-end",
63 | "sourcePortId": "b",
64 | "targetPortId": "t"
65 | }
66 | }
67 | ]
68 | }
--------------------------------------------------------------------------------
/packages/demo-x6/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/",
8 | "build.plugin.js"
9 | ]
10 | }
--------------------------------------------------------------------------------
/packages/g-react-renderer/README.md:
--------------------------------------------------------------------------------
1 | ### node 的样式的编写规则
2 |
3 | #### 是否可拖拽
4 |
5 | > 鼠标 mousedown 一个 node 节点并且移动的时候,是否可以移动画布上的内容
6 |
7 | ```tsx
8 | // 节点的属性
9 | export interface INodeProps {
10 | itemName: string;
11 | itemColor: string;
12 | img: string;
13 | itemDesc?: string;
14 | selected: boolean;
15 | hovered: boolean;
16 | error: boolean;
17 | // 是否可拖拽,默认可拖拽
18 | draggable?: boolean;
19 | }
20 |
21 | // 如果想要在某个shape上覆盖 nodeProps: INodeProps 上的 draggable 属性,可以按照如下设置
22 | const DemoNode = (props: INodeProps) => {
23 | return (
24 |
25 |
28 | 内容
29 |
30 |
31 | )
32 | }
33 |
34 | export default DemoNode;
35 | ```
36 |
37 | #### 透明度相关
38 |
39 | > - 透明度相关属性包括:opacity,fillOpacity,strokeOpacity, 适用的元素:Rect, Image;
40 | > - fillOpacity 和 strokeOpacity 的优先级高于 opacity;
41 | > - 透明度还需要考虑元素的父子嵌套关系,子元素的透明度 = 父元素的透明度 * 自身透明度,透明度默认值为:globalAlpha,未设置则为 1;
42 |
43 | - opacity: Number 范围 [0, 1]
44 | - fillOpacity: Number 范围 [0, 1]
45 | - strokeOpacity: Number 范围 [0, 1]
46 |
47 | ```tsx
48 | export interface IOpacityStyle {
49 | opacity?: number;
50 | fillOpacity?: number;
51 | strokeOpacity?: number;
52 | }
53 |
54 | const DemoNode = (props: INodeProps) => {
55 | return (
56 |
57 |
63 | 内容
64 |
65 |
66 | )
67 | }
68 | ```
69 |
70 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/g-react-renderer",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch",
15 | "prepublish": "npm run build"
16 | },
17 | "license": "MIT",
18 | "publishConfig": {
19 | "access": "public",
20 | "registry": "https://registry.npmjs.org/"
21 | },
22 | "devDependencies": {
23 | "@alib/build-scripts": "^0.1.18",
24 | "@ice/spec": "^0.1.1",
25 | "@types/ramda": "^0.27.1",
26 | "@types/react": "^16.3.0",
27 | "@types/react-dom": "^16.3.0",
28 | "build-plugin-component": "^1.10.0",
29 | "build-plugin-fusion": "^0.1.0",
30 | "build-plugin-modular-import": "^0.1.0",
31 | "build-plugin-moment-locales": "^0.1.0",
32 | "enzyme": "^3.11.0",
33 | "enzyme-adapter-react-16": "^1.14.0",
34 | "enzyme-to-json": "^3.5.0",
35 | "eslint": "^6.0.1",
36 | "jest-environment-enzyme": "^7.1.2",
37 | "lerna": "^5.1.8",
38 | "react": "^16.3.0",
39 | "react-dom": "^16.3.0",
40 | "react-test-renderer": "^16.8.6"
41 | },
42 | "dependencies": {
43 | "@antv/g-canvas": "^0.5.12",
44 | "@antv/util": "^2.0.14",
45 | "@react-pdf/font": "^2.0.10",
46 | "@react-pdf/layout": "^2.0.15",
47 | "@react-pdf/stylesheet": "^2.0.8",
48 | "@react-pdf/textkit": "^2.0.5",
49 | "@react-pdf/yoga": "^2.0.2",
50 | "list-diff2": "^0.1.4",
51 | "lodash": "^4.17.21",
52 | "ramda": "^0.27.1",
53 | "react-reconciler": "^0.26.2",
54 | "scheduler": "^0.20.2",
55 | "vision-types": "^1.0.4"
56 | },
57 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
58 | }
59 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/common/commonStyle.ts:
--------------------------------------------------------------------------------
1 | import {
2 | StyleSheet,
3 | } from '../renderer';
4 |
5 | export default StyleSheet.create({
6 | center: {
7 | alignSelf: 'center',
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/common/index.tsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alibaba/lowcode-graph/01df0d10dc3430e295678d11dd1a2eb5199f4b21/packages/g-react-renderer/src/common/index.tsx
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/components/index.tsx:
--------------------------------------------------------------------------------
1 | export { default as TextArea } from './textarea';
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/diff/render.js:
--------------------------------------------------------------------------------
1 | function render (vNode) {
2 | const element = document.createElement(vNode.tagName);
3 | const props = vNode.props;
4 |
5 | for (const key in props) {
6 | const value = props[key];
7 | element.setAttribute(key, value);
8 | }
9 |
10 | vNode.children && vNode.children( child => {
11 | const childElement = typeof child === 'string' ? document.createTextNode(child) : render(child);
12 | element.appendChild(childElement);
13 | });
14 |
15 | return element;
16 | }
17 |
18 | export default render;
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/diff/util.js:
--------------------------------------------------------------------------------
1 | function type(obj) {
2 | return Object.prototype.toString.call(obj).replace(/\[object\s|\]/g, "");
3 | }
4 |
5 | function isArray(list) {
6 | return type(list) === "Array";
7 | }
8 |
9 | function isObject(obj) {
10 | return type(obj) === "Object";
11 | }
12 |
13 | function isString(str) {
14 | return type(str) === "String";
15 | }
16 |
17 | function isNotEmptyObj(obj) {
18 | return isObject(obj) && JSON.stringify(obj) != "{}";
19 | }
20 |
21 | function objForEach(obj, fn) {
22 | isNotEmptyObj(obj) && Object.keys(obj).forEach(fn);
23 | }
24 |
25 | function aryForEach(ary, fn) {
26 | ary.length && ary.forEach(fn);
27 | }
28 |
29 | function setAttr(node, key, value) {
30 | switch (key) {
31 | case "style":
32 | node.style.cssText = value;
33 | break;
34 | case "value":
35 | var tagName = node.tagName || "";
36 | tagName = tagName.toLowerCase();
37 | if (tagName === "input" || tagName === "textarea") {
38 | node.value = value;
39 | } else {
40 | // if it is not a input or textarea, use `setAttribute` to set
41 | node.setAttribute(key, value);
42 | }
43 | break;
44 | default:
45 | node.setAttribute(key, value);
46 | break;
47 | }
48 | }
49 |
50 | function toArray(data) {
51 | if (!data) {
52 | return [];
53 | }
54 | const ary = [];
55 | aryForEach(data, item => {
56 | ary.push(item);
57 | });
58 |
59 | return ary;
60 | }
61 |
62 | export {
63 | isArray,
64 | isObject,
65 | isString,
66 | isNotEmptyObj,
67 | objForEach,
68 | aryForEach,
69 | setAttr,
70 | toArray,
71 | };
72 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/diff/vpatch.js:
--------------------------------------------------------------------------------
1 | const patch = {};
2 | patch.REMOVE = 0;
3 | patch.REPLACE = 1; // node replace
4 | patch.TEXT = 2; // text replace
5 | patch.PROPS = 3;
6 | patch.INSERT = 4;
7 | patch.REORDER = 5;
8 |
9 | export default patch;
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/elements.ts:
--------------------------------------------------------------------------------
1 | export const Shape = 'SHAPE';
2 | export const Rect = 'RECT';
3 | export const Text = 'TEXT';
4 | export const Image = 'IMAGE';
5 | export const IconFont = 'ICONFONT';
6 | export const TextInstance = 'TEXT_INSTANCE';
7 |
8 | declare global {
9 | namespace JSX {
10 | interface IntrinsicElements {
11 | 'SHAPE': {
12 | name?: string;
13 | link?: string;
14 | style?: any;
15 | children?: any;
16 | },
17 | 'RECT': {
18 | name: string;
19 | link?: string;
20 | style?: any;
21 | children?: any;
22 | },
23 | 'IMAGE': {
24 | name: string;
25 | src: string;
26 | link?: string;
27 | style?: any;
28 | clip?: string;
29 | alt?: string;
30 | },
31 | 'TEXT': {
32 | name: string,
33 | link?: string;
34 | style?: any,
35 | children?: any,
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/index.ts:
--------------------------------------------------------------------------------
1 | import './main.scss';
2 | import { GRenderer } from './renderer';
3 |
4 | export * from './components';
5 |
6 | export * from './renderer';
7 |
8 | export { GRenderer };
9 |
10 | export default GRenderer;
11 |
12 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/layout/index.js:
--------------------------------------------------------------------------------
1 | import * as R from 'ramda';
2 |
3 | import resolveStyles from './resolveStyles';
4 | import resolveDimensions from './resolveDimensions';
5 |
6 | const layout = R.compose(
7 | resolveDimensions,
8 | resolveStyles,
9 | );
10 |
11 | export default layout;
12 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/layout/resolveStyles.js:
--------------------------------------------------------------------------------
1 | import * as R from 'ramda';
2 | import stylesheet from '@react-pdf/stylesheet';
3 |
4 | /**
5 | * Resolves node styles
6 | *
7 | * @param {Object} container
8 | * @param {Object} document node
9 | * @returns {Object} node (and subnodes) with resolved styles
10 | */
11 | const resolveNodeStyles = container => node => {
12 | return R.evolve({
13 | style: stylesheet(container),
14 | children: R.map(resolveNodeStyles(container)),
15 | })(node);
16 | };
17 |
18 | /**
19 | * Resolves shape styles
20 | *
21 | * @param {Object} shape
22 | * @returns {Object} document page with resolved styles
23 | */
24 | const resolveShapeStyles = shape => {
25 | const box = R.prop('box', shape);
26 | const style = R.prop('style', shape);
27 | const container = R.isEmpty(box) ? style : box;
28 |
29 | return R.evolve({
30 | style: stylesheet(container),
31 | children: R.map(resolveNodeStyles(container)),
32 | })(shape);
33 | };
34 |
35 | /**
36 | * Resolves document styles
37 | *
38 | * @param {Object} document root
39 | * @returns {Object} document root with resolved styles
40 | */
41 | export default R.evolve({
42 | children: R.map(resolveShapeStyles),
43 | });
44 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/layout/resolveTextLayout.ts:
--------------------------------------------------------------------------------
1 | import * as R from 'ramda';
2 | //@ts-ignore
3 | import * as P from '@react-pdf/primitives';
4 | //@ts-ignore
5 | import layoutText from '@react-pdf/layout/lib/text/layoutText';
6 |
7 | const isType = R.propEq('type');
8 |
9 | const isSvg = isType(P.Svg);
10 |
11 | const isText = isType(P.Text);
12 |
13 | const isNotSvg = R.complement(isSvg);
14 |
15 | const shouldLayoutText = (node: any) => isText(node) && !node.lines;
16 |
17 | /**
18 | * Performs text layout on text node if wasn't calculated before.
19 | * Text layout is usually performed on Yoga's layout process (via setMeasureFunc),
20 | * but we need to layout those nodes with fixed width and height.
21 | *
22 | * @param {Object} node
23 | * @returns {Object} layout node
24 | */
25 | // @ts-ignore
26 | const resolveTextLayout = (node: any) => {
27 | // @ts-ignore
28 | const mapChild = (child: any) => resolveTextLayout(child);
29 |
30 | return R.compose(
31 | R.evolve({
32 | children: R.map(R.when(isNotSvg, mapChild)),
33 | }),
34 | R.when(
35 | shouldLayoutText,
36 | R.compose(
37 | R.converge(R.assoc('lines'), [
38 | R.converge(layoutText, [
39 | R.identity,
40 | R.path(['box', 'width']),
41 | R.path(['box', 'height']),
42 | ]),
43 | R.identity,
44 | ]),
45 | ),
46 | ),
47 | )(node);
48 | };
49 |
50 | export default resolveTextLayout;
51 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/locale/en-us.js:
--------------------------------------------------------------------------------
1 | export default {
2 | locale: 'en-us',
3 | name: 'My Name',
4 | };
5 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/locale/zh-cn.js:
--------------------------------------------------------------------------------
1 | export default {
2 | locale: 'zh-cn',
3 | name: '我的名字',
4 | };
5 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/main.scss:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alibaba/lowcode-graph/01df0d10dc3430e295678d11dd1a2eb5199f4b21/packages/g-react-renderer/src/main.scss
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/elements/renderImage.ts:
--------------------------------------------------------------------------------
1 | import { getShadowStyle, getOpacityStyle } from '../../util';
2 |
3 | const renderImage = (group: any) => (node: any) => {
4 | const { left, top, width, height } = node.box;
5 | const { clip, src, draggable } = node.props;
6 | const {
7 | shadowOffsetX,
8 | shadowOffsetY,
9 | shadowBlur,
10 | shadowColor,
11 | opacity,
12 | fillOpacity,
13 | strokeOpacity,
14 | cursor,
15 | zIndex,
16 | } = node.style;
17 | let nodeDraggable = node.draggable || draggable;
18 | nodeDraggable = nodeDraggable !== false;
19 |
20 | // shadow
21 | const shadowStyle = getShadowStyle({
22 | shadowOffsetX,
23 | shadowOffsetY,
24 | shadowBlur,
25 | shadowColor,
26 | });
27 |
28 | // opacity
29 | const opacityStyle = getOpacityStyle({
30 | opacity,
31 | fillOpacity,
32 | strokeOpacity,
33 | });
34 |
35 | const image = group.addShape('image', {
36 | attrs: {
37 | // Common Attr
38 | name: `g-react-item-${node.name || Math.random()}`,
39 | // 暂未支持,可实现
40 | // fill,stroke,lineWidth,lineDash
41 | ...shadowStyle,
42 | ...opacityStyle,
43 | cursor,
44 | // Image Attr
45 | x: left,
46 | y: top,
47 | width,
48 | height,
49 | img: src,
50 | },
51 | draggable: nodeDraggable,
52 | zIndex: zIndex || 0
53 | });
54 |
55 |
56 | if (clip === 'circle') {
57 | // clip circle, 3.2.10暂不支持
58 | image.setClip({
59 | type: 'circle', // 支持 circle、rect、ellipse、Polygon 及自定义 path clip
60 | attrs: {
61 | r: width / 2,
62 | x: left + width / 2,
63 | y: top + height / 2,
64 | }
65 | });
66 | }
67 |
68 | return node;
69 | };
70 | export const updateImage = (targetElement: any) => (newProps: any, newStyle: any, oldVNode: any) => {
71 | // 优先使用新的clip属性
72 | const clip = newProps.clip || oldVNode.props.clip;
73 | let newAttr = { ...newProps };
74 | if (newProps.left !== undefined) {
75 | newAttr.x = newProps.left;
76 | }
77 | if (newProps.top !== undefined) {
78 | newAttr.y = newProps.top;
79 | }
80 |
81 | if (Object.keys(newStyle).length > 0) {
82 | const {
83 | // 阴影
84 | shadowOffsetX,
85 | shadowOffsetY,
86 | shadowBlur,
87 | shadowColor,
88 | // 透明度
89 | opacity,
90 | fillOpacity,
91 | strokeOpacity,
92 | } = newStyle;
93 | // shadow
94 | const shadowStyle = getShadowStyle({
95 | shadowOffsetX,
96 | shadowOffsetY,
97 | shadowBlur,
98 | shadowColor,
99 | });
100 | // opacity
101 | const opacityStyle = getOpacityStyle({
102 | opacity,
103 | fillOpacity,
104 | strokeOpacity,
105 | });
106 | // assign
107 | newAttr = {
108 | ...newAttr,
109 | ...shadowStyle,
110 | ...opacityStyle,
111 | };
112 | }
113 |
114 | targetElement.attr(newAttr);
115 |
116 | if (clip === 'circle') {
117 | // clip circle, 3.2.10暂不支持
118 | const width = newAttr.width || oldVNode.box.width;
119 | const height = newAttr.height || oldVNode.box.height;
120 | const left = newAttr.left || oldVNode.box.left;
121 | const top = newAttr.top || oldVNode.box.top;
122 | targetElement.setClip({
123 | type: 'circle', // 支持 circle、rect、ellipse、Polygon 及自定义 path clip
124 | attrs: {
125 | r: width / 2,
126 | x: left + width / 2,
127 | y: top + height / 2,
128 | }
129 | });
130 | }
131 | }
132 |
133 | export default renderImage;
134 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/elements/renderNode.ts:
--------------------------------------------------------------------------------
1 | // @ts-ignore
2 | import * as R from 'ramda';
3 | import * as ELE from '../../elements';
4 | import transform from '../operations/transform';
5 |
6 | import renderText from '../elements/renderText';
7 | import renderRect from '../elements/renderRect';
8 | import renderImage from '../elements/renderImage';
9 |
10 | const isText = R.propEq('type', ELE.Text);
11 | const isRect = R.propEq('type', ELE.Rect);
12 | const isImage = R.propEq('type', ELE.Image);
13 |
14 | const shouldRenderChildren = (v: any) => !isText(v);
15 |
16 | const renderChildren = (nodeGroup: any) => (node: any) => {
17 | const subGroup = nodeGroup.addGroup();
18 |
19 | if (node.box) {
20 | // 偏移子元素
21 | subGroup.translate(node.box.left, node.box.top);
22 | }
23 |
24 | R.compose(R.forEach(renderNode(subGroup)), R.pathOr([], ['children']))(node);
25 | return node;
26 | };
27 |
28 | const renderNode = (nodeGroup: any) => (node: any) => {
29 | return R.compose(
30 | R.when(shouldRenderChildren, renderChildren(nodeGroup)),
31 | R.cond([
32 | [isText, renderText(nodeGroup)],
33 | [isRect, renderRect(nodeGroup)],
34 | [isImage, renderImage(nodeGroup)],
35 | [R.T, R.identity],
36 | ]),
37 | transform(nodeGroup),
38 | // @ts-ignore
39 | )(node)
40 | };
41 |
42 | export default renderNode;
43 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/elements/renderRect.ts:
--------------------------------------------------------------------------------
1 | import { getShadowStyle, getOpacityStyle } from "../../util";
2 | import { isNumber } from 'lodash';
3 |
4 | export const renderRect = (nodeGroup: any) => (node: any) => {
5 | const { top, left, width, height } = node.box;
6 | const { link, draggable } = node.props;
7 | let nodeDraggable = node.draggable || draggable;
8 | nodeDraggable = nodeDraggable !== false;
9 | const {
10 | backgroundColor,
11 | borderTopLeftRadius,
12 | borderBottomRightRadius,
13 | borderBottomLeftRadius,
14 | borderTopRightRadius,
15 | borderColor,
16 | borderLeftWidth,
17 | borderLeftColor,
18 | shadowOffsetX,
19 | shadowOffsetY,
20 | shadowBlur,
21 | shadowColor,
22 | opacity,
23 | fillOpacity,
24 | strokeOpacity,
25 | cursor,
26 | // 旋转角度
27 | rotate,
28 | // 偏移 {x: number, y: number }
29 | translate,
30 | zIndex
31 | } = node.style;
32 |
33 | // FIXME: HACK
34 | const lineWidth = borderLeftWidth || 0;
35 | const stroke = borderLeftColor || '';
36 | const radius = [
37 | borderTopLeftRadius || 0,
38 | borderTopRightRadius || 0,
39 | borderBottomRightRadius || 0,
40 | borderBottomLeftRadius || 0,
41 | ];
42 |
43 | // shadow
44 | const shadowStyle = getShadowStyle({
45 | shadowOffsetX,
46 | shadowOffsetY,
47 | shadowBlur,
48 | shadowColor,
49 | });
50 |
51 | // opacity
52 | const opacityStyle = getOpacityStyle({
53 | opacity,
54 | fillOpacity,
55 | strokeOpacity,
56 | });
57 |
58 | const rect = nodeGroup.addShape('rect', {
59 | attrs: {
60 | // Common Attr
61 | name: `g-react-item-${node.name || Math.random()}`,
62 | fill: backgroundColor,
63 | stroke,
64 | lineWidth,
65 | // 暂未支持,可实现
66 | // lineDash,
67 | ...shadowStyle,
68 | ...opacityStyle,
69 | cursor: cursor || (link && 'pointer'),
70 | // Rect Attr
71 | x: left,
72 | y: top,
73 | width,
74 | height,
75 | radius,
76 | // Extra Attr
77 | link,
78 | },
79 | draggable: nodeDraggable,
80 | zIndex: zIndex || 0
81 | });
82 |
83 | if (rotate) {
84 | rect.rotate(rotate);
85 | }
86 |
87 | if (translate && (isNumber(translate.x) && isNumber(translate.y))) {
88 | rect.translate(translate.x, translate.y);
89 | }
90 |
91 | return node;
92 | };
93 |
94 | export const updateRect = (targetElement: any) => (newProps: any, newStyle: any) => {
95 | let newAttr = { ...newProps };
96 | if (newProps.left !== undefined) {
97 | newAttr.x = newProps.left;
98 | }
99 | if (newProps.top !== undefined) {
100 | newAttr.y = newProps.top;
101 | }
102 | if (Object.keys(newStyle).length > 0) {
103 | const {
104 | backgroundColor,
105 | borderTopLeftRadius,
106 | borderBottomRightRadius,
107 | borderBottomLeftRadius,
108 | borderTopRightRadius,
109 | borderLeftWidth,
110 | borderLeftColor,
111 | shadowOffsetX,
112 | shadowOffsetY,
113 | shadowBlur,
114 | shadowColor,
115 | opacity,
116 | fillOpacity,
117 | strokeOpacity,
118 | } = newStyle;
119 |
120 | // FIXME: HACK
121 | const lineWidth = borderLeftWidth || 0;
122 | const stroke = borderLeftColor || '';
123 | const radius = [
124 | borderTopLeftRadius || 0,
125 | borderTopRightRadius || 0,
126 | borderBottomRightRadius || 0,
127 | borderBottomLeftRadius || 0,
128 | ];
129 |
130 | // shadow
131 | const shadowStyle = getShadowStyle({
132 | shadowOffsetX,
133 | shadowOffsetY,
134 | shadowBlur,
135 | shadowColor,
136 | });
137 |
138 | // opacity
139 | const opacityStyle = getOpacityStyle({
140 | opacity,
141 | fillOpacity,
142 | strokeOpacity,
143 | });
144 |
145 | newAttr = {
146 | ...newAttr,
147 | fill: backgroundColor,
148 | radius,
149 | stroke,
150 | lineWidth,
151 | ...shadowStyle,
152 | ...opacityStyle,
153 | };
154 | }
155 | targetElement.attr(newAttr);
156 | }
157 |
158 | export default renderRect;
159 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/elements/rendererLink.ts:
--------------------------------------------------------------------------------
1 | // 该区域可点击
2 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/index.ts:
--------------------------------------------------------------------------------
1 | import renderNode from './elements/renderNode';
2 |
3 | const renderShape = (gContainerGroup: any) => (shape: any) => {
4 | // create parent group
5 | const shapeGroup = gContainerGroup.addGroup();
6 | const elements = shape.children || [];
7 | elements.forEach(renderNode(shapeGroup));
8 | };
9 |
10 | const render = (gContainerGroup: any, shape: any) => {
11 | renderShape(gContainerGroup)(shape);
12 | return gContainerGroup;
13 | };
14 |
15 | export default render;
16 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/render/operations/transform.ts:
--------------------------------------------------------------------------------
1 | import * as R from 'ramda';
2 |
3 | const applySingleTransformation = (ctx: any, transform: any) => {
4 | const { operation, value } = transform;
5 |
6 | switch (operation) {
7 | case 'scale': {
8 | const [scaleX, scaleY] = value;
9 | // ctx.scale(scaleX, scaleY, { origin });
10 | break;
11 | }
12 |
13 | case 'rotate': {
14 | const [angle] = value;
15 | // ctx.rotate(angle, { origin });
16 | break;
17 | }
18 |
19 | case 'translate': {
20 | const [x, y] = value;
21 | // ctx.translate(x, y, { origin });
22 | break;
23 | }
24 |
25 | case 'matrix': {
26 | // ctx.transform(...value);
27 | break;
28 | }
29 |
30 | default: {
31 | console.error(`Transform operation: '${operation}' doesn't supported`);
32 | }
33 | }
34 | };
35 |
36 | const applyTransformations = (ctx: any, node: any) => {
37 | if (!node.origin) return node;
38 |
39 | const origin = [node.origin.left, node.origin.top];
40 | const operations = (node.style && node.style.transform) || [];
41 |
42 | operations.forEach((operation: any) => {
43 | applySingleTransformation(ctx, operation);
44 | });
45 |
46 | return node;
47 | };
48 |
49 | export default R.curryN(2, applyTransformations);
50 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/renderer/g.ts:
--------------------------------------------------------------------------------
1 | // render to g6 node
2 | import layoutShape from '../layout';
3 | import renderShape from '../render';
4 | import { createRenderer } from './renderer';
5 | import diff from '../diff';
6 | import patch from '../diff/patches';
7 | export class GRenderer {
8 |
9 | constructor(element: any, renderer?: any, callback?: any) {
10 | this.element = element;
11 | this.renderer = renderer;
12 | this.callback = callback;
13 | }
14 |
15 | element: any = null;
16 | renderer: any = null;
17 | callback: any = null;
18 | layout: any = null;
19 | newLayout: any = null;
20 |
21 | calculateLayout() {
22 | const container = { type: 'ROOT', shape: null };
23 |
24 | let renderer = this.renderer || createRenderer({ onChange: this.callback ? this.callback : () => { } });
25 |
26 | const newRoot = renderer.createContainer(container);
27 |
28 | const updateContainer = element => {
29 | renderer.updateContainer(element, newRoot, null);
30 | };
31 |
32 | if (this.element) updateContainer(this.element);
33 | // cache layout
34 | this.layout = layoutShape(container.shape);
35 | return this.layout;
36 | }
37 |
38 | reCalculateLayout(element: any) {
39 | this.element = element;
40 | const container = { type: 'ROOT', shape: null };
41 |
42 | let renderer = this.renderer || createRenderer({ onChange: this.callback ? this.callback : () => { } });
43 |
44 | const newRoot = renderer.createContainer(container);
45 |
46 | const updateContainer = (element: any) => {
47 | renderer.updateContainer(element, newRoot, null);
48 | };
49 |
50 | if (this.element) updateContainer(this.element);
51 | // cache layout
52 | this.newLayout = layoutShape(container.shape);
53 | return this.newLayout;
54 | }
55 |
56 | render(gGroupContainer: any, layoutInfo: any) {
57 | const layout = layoutInfo || this.layout || this.calculateLayout();
58 |
59 | return renderShape(gGroupContainer, layout);
60 | }
61 |
62 | update(gGroupContainer: any, layoutInfo: any, element: any) {
63 | // 更新节点 兜底第一次渲染
64 | const layout = layoutInfo || this.newLayout || this.reCalculateLayout(element) || this.layout || this.calculateLayout();
65 | const patches = diff(this.layout, this.newLayout);
66 | patch(this.layout, patches, gGroupContainer);
67 | // 更新完节点把最新layout覆盖老layout
68 | this.layout = this.newLayout;
69 |
70 | // const shape = gGroupContainer.findAll(
71 | // (allItem: any) => allItem.attr('name') && allItem.attr('name').indexOf('g-react') > -1,
72 | // );
73 | // shape.forEach((shapeItem: any) => {
74 | // shapeItem.remove();
75 | // });
76 | // return renderShape(gGroupContainer, layout);
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/renderer/index.ts:
--------------------------------------------------------------------------------
1 | const StyleSheet = {
2 | create: (s: any) => s,
3 | };
4 |
5 | export { StyleSheet };
6 |
7 | export { GRenderer } from './g';
8 |
9 | export * from '../elements';
10 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/renderer/propsEqual.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-continue */
2 |
3 | /**
4 | * Checks if two sets of props are equal (recursively)
5 | *
6 | * @param {Object} props A
7 | * @param {Object} props B
8 | * @returns {Boolean} props equals?
9 | *
10 | */
11 | const propsEqual = (a, b) => {
12 | const oldPropsKeys = Object.keys(a);
13 | const newPropsKeys = Object.keys(b);
14 |
15 | if (oldPropsKeys.length !== newPropsKeys.length) {
16 | return false;
17 | }
18 |
19 | for (let i = 0; i < oldPropsKeys.length; i += 1) {
20 | const propName = oldPropsKeys[i];
21 |
22 | if (propName === 'render' && !a[propName] !== !b[propName]) {
23 | return false;
24 | }
25 |
26 | if (propName !== 'children' && a[propName] !== b[propName]) {
27 | if (
28 | typeof a[propName] === 'object' &&
29 | typeof b[propName] === 'object' &&
30 | propsEqual(a[propName], b[propName])
31 | ) {
32 | continue;
33 | }
34 |
35 | return false;
36 | }
37 |
38 | if (
39 | propName === 'children' &&
40 | (typeof a[propName] === 'string' || typeof b[propName] === 'string')
41 | ) {
42 | return a[propName] === b[propName];
43 | }
44 | }
45 |
46 | return true;
47 | };
48 |
49 | export default propsEqual;
50 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/scss/variables.scss:
--------------------------------------------------------------------------------
1 | @charset "UTF-8";
2 |
3 | /* write style here */
4 | @import '~@alifd/next/variables.scss';
5 |
6 | // 必须设置,请严格遵循该包含default的写法。
7 | $css-prefix: 'next-' !default;
8 | $deep-css-prefix: 'deep-' !default;
9 |
10 | // 使用到的主题扩展变量,内置默认值
11 | $color-fill1-5: #BBC3CC !default;
12 | $color-fill1-6: #A5AFBC !default;
13 | $color-fill1-7: #79889B !default;
14 | $color-fill1-8: #4C6079 !default;
15 | $color-fill1-9: #1F3858 !default;
16 |
17 | // default for theme-97
18 | $color-brand1-2: #FFF0E5 !default;
19 | $color-brand1-3: #FFF7F0 !default;
20 |
21 | // default for theme-254
22 | //$color-brand1-2: #E5F1FD !default;
23 | //$color-brand1-3: #F0F7FF !default;
24 |
25 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/util/index.ts:
--------------------------------------------------------------------------------
1 | import { get, isNumber } from 'lodash';
2 |
3 | interface IShadowStyle {
4 | shadowOffsetX?: string | number;
5 | shadowOffsetY?: string | number
6 | shadowBlur?: string | number
7 | shadowColor?: string
8 | }
9 |
10 | interface IOpacityStyle {
11 | opacity?: number;
12 | fillOpacity?: number;
13 | strokeOpacity?: number;
14 | }
15 |
16 | const localeTemp = get(window, 'g_config.locale') || 'zh_CN';
17 | const locale = /^zh[-_]cn$/i.test(localeTemp) ? 'zh_CN' : 'en_US'
18 | const varList = get(window, `arrangement[${locale}]`) || {};
19 |
20 | export const i18n = (key: string) => {
21 | return varList[key] || key;
22 | }
23 |
24 | export const formatCssNumber = (value: number | string) => {
25 | const val = +(`${value}`).replace('px', '');
26 | return isNaN(val) ? 0 : val;
27 | }
28 |
29 | /**
30 | * 判断是否合法的透明度值,范围是 [0, 1]
31 | * @param value Number 透明度值
32 | * @returns Boolean
33 | */
34 | export const isAlphaNumber = (value: any): boolean => {
35 | return isNumber(value) && (value >= 0 && value <= 1);
36 | }
37 |
38 | /**
39 | * 获取元素的阴影样式
40 | * @param style IShadowStyle 阴影样式
41 | * @returns IShadowStyle
42 | */
43 | export const getShadowStyle = (style: IShadowStyle): IShadowStyle => {
44 | const {
45 | shadowOffsetX,
46 | shadowOffsetY,
47 | shadowBlur,
48 | shadowColor,
49 | } = style;
50 | const shadowStyle = {};
51 | if (shadowColor) {
52 | Object.assign(shadowStyle, {
53 | shadowColor,
54 | shadowBlur: formatCssNumber(shadowBlur || 0),
55 | shadowOffsetX: formatCssNumber(shadowOffsetX || 0),
56 | shadowOffsetY: formatCssNumber(shadowOffsetY || 0),
57 | })
58 | }
59 | return shadowStyle;
60 | }
61 |
62 | /**
63 | * 获取元素的透明度样式
64 | * @param style IOpacityStyle 透明度样式
65 | * @returns IOpacityStyle
66 | */
67 | export const getOpacityStyle = (style: IOpacityStyle): IOpacityStyle => {
68 | const opacityStyle: IOpacityStyle = {};
69 | Object.keys(style).forEach(key => {
70 | const value = style[key as keyof IOpacityStyle]
71 | if (isAlphaNumber(value)) {
72 | opacityStyle[key as keyof IOpacityStyle] = value;
73 | }
74 | });
75 | return opacityStyle;
76 | }
77 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/util/measureExactText.ts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alibaba/lowcode-graph/01df0d10dc3430e295678d11dd1a2eb5199f4b21/packages/g-react-renderer/src/util/measureExactText.ts
--------------------------------------------------------------------------------
/packages/g-react-renderer/src/util/measureText.ts:
--------------------------------------------------------------------------------
1 | import { isString, memoize, values } from '@antv/util';
2 |
3 | let ctx: CanvasRenderingContext2D | null;
4 |
5 | /**
6 | * 获取 canvas context
7 | */
8 | export function getCanvasContext() {
9 | if (!ctx) {
10 | ctx = document.createElement('canvas').getContext('2d');
11 | }
12 |
13 | return ctx;
14 | }
15 |
16 | /**
17 | * 计算文本在画布中的宽高
18 | * @param text 文本
19 | * @param font 字体
20 | */
21 | export const measureText = memoize(
22 | (text: string, font: any = {}): any => {
23 | const { fontSize = 12, fontFamily = 'PingFang SC', fontWeight, fontStyle, fontVariant } = font;
24 |
25 | // return {
26 | // // 使用近似的文本高度
27 | // // https://stackoverflow.com/questions/1134586/how-can-you-find-the-height-of-text-on-an-html-canvas
28 | // height: fontSize * 1.2,
29 | // width: fontSize * text.length,
30 | // };
31 |
32 | const ctx = getCanvasContext();
33 | if (ctx) {
34 | // @see https://developer.mozilla.org/zh-CN/docs/Web/CSS/font
35 | ctx.font = [fontStyle, fontWeight, fontVariant, `${fontSize}px`, fontFamily].join(' ');
36 | const metrics = ctx.measureText(isString(text) ? text : '');
37 | // metrics
38 | // actualBoundingBoxAscent: 8.63671875
39 | // actualBoundingBoxDescent: 2.654296875
40 | // actualBoundingBoxLeft: -1.025390625
41 | // actualBoundingBoxRight: 98.595703125
42 | // fontBoundingBoxAscent: 11
43 | // fontBoundingBoxDescent: 3
44 | // width: 99.380859375
45 | return {
46 | // 使用近似的文本高度
47 | // https://stackoverflow.com/questions/1134586/how-can-you-find-the-height-of-text-on-an-html-canvas
48 | height: fontSize * 1.2,
49 | width: metrics.width,
50 | };
51 | }
52 | return {
53 | height: 0,
54 | width: 0,
55 | };
56 | },
57 | (text: string, font = {}) => [text, ...values(font)].join('')
58 | );
59 |
--------------------------------------------------------------------------------
/packages/g-react-renderer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-core/README.md:
--------------------------------------------------------------------------------
1 | ## lowcode-graph-core
2 |
--------------------------------------------------------------------------------
/packages/plugin-core/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-core",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch",
15 | "prepublish": "npm run build"
16 | },
17 | "license": "MIT",
18 | "publishConfig": {
19 | "access": "public",
20 | "registry": "https://registry.npmjs.org/"
21 | },
22 | "dependencies": {
23 | "@alilc/lowcode-plugin-inject": "^1.2.2"
24 | },
25 | "devDependencies": {
26 | "@alib/build-scripts": "^0.1.18",
27 | "build-plugin-component": "^1.10.0"
28 | },
29 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
30 | }
31 |
--------------------------------------------------------------------------------
/packages/plugin-core/src/index.ts:
--------------------------------------------------------------------------------
1 | import { AssetLoader } from '@alilc/lowcode-utils';
2 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
3 | import Inject, { injectAssets } from '@alilc/lowcode-plugin-inject';
4 |
5 | const PluginCore = (ctx: IPublicModelPluginContext, options: any) => {
6 | return {
7 | async init() {
8 | const { material, project, plugins } = ctx;
9 | const { assets, schema } = options;
10 | await plugins.register(Inject);
11 | // 注册 components,加载所有的 meta js
12 | await material.setAssets(await injectAssets(assets));
13 |
14 | // 加载 schema
15 | project.openDocument(schema);
16 |
17 | // 简单处理 init 时候直接 load 所有组件 view
18 | const loader = new AssetLoader();
19 | const componentsAssets = assets.packages.map((asset: any) => asset.urls).flat();
20 | await loader.load(componentsAssets);
21 | },
22 | }
23 | }
24 |
25 | PluginCore.pluginName = 'PluginCore';
26 | PluginCore.meta = {
27 | preferenceDeclaration: {
28 | title: '参数定义',
29 | properties: [
30 | {
31 | key: 'assets',
32 | type: 'object',
33 | description: '资产包',
34 | },
35 | {
36 | key: 'schema',
37 | type: 'object',
38 | description: 'schema 描述',
39 | },
40 | ],
41 | },
42 | }
43 | export default PluginCore;
--------------------------------------------------------------------------------
/packages/plugin-core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/README.md:
--------------------------------------------------------------------------------
1 | ## g6 画布插件
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-g6-designer",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch",
15 | "prepublish": "npm run build"
16 | },
17 | "license": "MIT",
18 | "publishConfig": {
19 | "access": "public",
20 | "registry": "https://registry.npmjs.org/"
21 | },
22 | "dependencies": {
23 | "@alilc/lce-graph-tools": "1.0.12",
24 | "@alilc/lowcode-utils": "^1.0.0",
25 | "@antv/hierarchy": "^0.6.8"
26 | },
27 | "devDependencies": {
28 | "@alib/build-scripts": "^0.1.18",
29 | "@antv/g6": "^4.6.4",
30 | "build-plugin-component": "^1.10.0"
31 | },
32 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
33 | }
34 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/DesignerView.tsx:
--------------------------------------------------------------------------------
1 | import { createElement, PureComponent } from "react";
2 | import { ILowCodePluginContext, project } from '@alilc/lowcode-engine';
3 | import { Editor } from '@alilc/lowcode-editor-core';
4 | import { initTreeGraph, initGraph } from "./graph/initGraph";
5 | import Items from "./items";
6 | import { render } from "react-dom";
7 | import { CommandManager } from "@alilc/lce-graph-tools";
8 | import { Graph, TreeGraph } from "@antv/g6";
9 | import { rootState } from './items/state';
10 | import g6Designer from "./designer";
11 |
12 | interface IProps {
13 | editor: Editor;
14 | ctx: ILowCodePluginContext;
15 | commandManager: CommandManager;
16 | type: 'tree' | 'graph';
17 | graphConfig?: any;
18 | layoutConfig?: any;
19 | }
20 |
21 |
22 |
23 | export default class DesignerView extends PureComponent {
24 | private container: HTMLDivElement;
25 | private nodesContainer: HTMLDivElement;
26 |
27 | refContainer = (container: HTMLDivElement) => {
28 | this.container = container;
29 | }
30 |
31 | refNodesContainer = (container: HTMLDivElement) => {
32 | this.nodesContainer = container;
33 | }
34 |
35 | componentDidMount() {
36 | g6Designer.setGraphConfig(this.props.graphConfig || {});
37 | g6Designer.setLayoutConfig(this.props.layoutConfig || {});
38 |
39 | // @ts-ignore
40 | let graph: Graph | TreeGraph | null = null;
41 | if (rootState.type === 'tree') {
42 | // 树布局
43 | graph = initTreeGraph(this.container, rootState.getGraphData.bind(rootState));
44 | } else {
45 | // 图布局
46 | graph = initGraph(this.container);
47 | }
48 | if (graph) {
49 | // g6Designer start
50 | g6Designer.init(this.props.ctx, graph);
51 |
52 | render(
53 | createElement(Items, {
54 | graph
55 | }),
56 | this.nodesContainer
57 | );
58 | }
59 | }
60 |
61 | render() {
62 | return (
63 |
66 | )
67 | }
68 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/builtin-commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from './insert-item';
2 | export * from './remove-item';
3 | export * from './zoom-in-out';
4 | export * from './undo-redo';
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/builtin-commands/insert-item.ts:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
2 | export const insertChild = (ctx: ILowCodePluginContext, graph: any, options: { selectedNodes: any, model: any }) => {
3 | const { model } = options;
4 | const node = ctx.project.currentDocument?.createNode(model)
5 | ctx.project.currentDocument?.insertNode(options.selectedNodes[0], node);
6 | }
7 |
8 | export const insertSibling = (ctx: ILowCodePluginContext, graph: any, options: { selectedNodes: any, model: any }) => {
9 | const { model } = options;
10 | const node = ctx.project.currentDocument?.createNode(model);
11 | const currentSelectedNode = options.selectedNodes[0];
12 | ctx.project.currentDocument?.insertNode(currentSelectedNode.parent, node, currentSelectedNode.index + 1);
13 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/builtin-commands/remove-item.ts:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
2 | export const removeItem = (ctx: ILowCodePluginContext, graph: any, selectedNodes: any[]) => {
3 | selectedNodes?.forEach(node => { ctx.project.currentDocument?.removeNode(node.id) });
4 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/builtin-commands/undo-redo.ts:
--------------------------------------------------------------------------------
1 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
2 | export const undo = (ctx: IPublicModelPluginContext, graph: any) => {
3 | ctx.project.currentDocument?.history.back();
4 | }
5 | export const redo = (ctx: IPublicModelPluginContext, graph: any) => {
6 | ctx.project.currentDocument?.history.forward();
7 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/builtin-commands/zoom-in-out.ts:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
2 | export const zoomIn = (ctx: ILowCodePluginContext, graph: any) => {
3 | graph.zoom(1.1);
4 | }
5 | export const zoomOut = (ctx: ILowCodePluginContext, graph: any) => {
6 | graph.zoom(0.9);
7 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/behaviors/click-item.ts:
--------------------------------------------------------------------------------
1 | import { isEdge, getGraphState, clearSelectedState } from '../utils';
2 | import { ItemState, GraphState, EditorEvent } from '@alilc/lce-graph-tools';
3 | import { Behavior } from '../interfaces';
4 | import * as G6 from '@antv/g6';
5 | import { get } from 'lodash';
6 | import { rootState } from '../../items/state';
7 |
8 | export interface ClickItemBehavior extends Behavior {
9 | /** 处理点击事件 */
10 | handleItemClick({ item }: { item: G6.Item }): void;
11 | /** 处理画布点击 */
12 | handleCanvasClick(): void;
13 | /** 处理按键按下 */
14 | handleKeyDown(e: KeyboardEvent): void;
15 | /** 处理按键抬起 */
16 | handleKeyUp(e: KeyboardEvent): void;
17 | }
18 |
19 | export interface DefaultConfig {
20 | /** 是否支持多选 */
21 | multiple: boolean;
22 | /** 是否按下多选 */
23 | keydown: boolean;
24 | /** 多选按键码值 */
25 | keyCode: number;
26 | }
27 |
28 | const clickItemBehavior: ClickItemBehavior & ThisType = {
29 | getDefaultCfg(): DefaultConfig {
30 | return {
31 | multiple: true,
32 | keydown: false,
33 | keyCode: 16,
34 | };
35 | },
36 |
37 | getEvents() {
38 | return {
39 | 'node:click': 'handleItemClick',
40 | 'edge:click': 'handleItemClick',
41 | 'canvas:click': 'handleCanvasClick',
42 | keydown: 'handleKeyDown',
43 | keyup: 'handleKeyUp',
44 | };
45 | },
46 |
47 | handleItemClick(e) {
48 | const eleName = get(e, 'target.attrs.name');
49 | if (eleName === 'collapse') return;
50 | const { item } = e;
51 | if (isEdge(item)) {
52 | return;
53 | }
54 | const nodeId = item.get('id');
55 | const isSelected = item && item.hasState(ItemState.Selected);
56 | if (this.multiple && this.keydown) {
57 | const currentSelected = rootState.selected;
58 | if (isSelected) {
59 | rootState.setSelected(currentSelected.filter(itemId => itemId !== nodeId));
60 | } else {
61 | rootState.setSelected(currentSelected.concat(nodeId));
62 | }
63 | } else {
64 | if (isSelected) {
65 | rootState.setSelected([]);
66 | } else {
67 | rootState.setSelected([nodeId]);
68 | }
69 | }
70 | },
71 |
72 | handleCanvasClick() {
73 | const { graph } = this;
74 | clearSelectedState(graph!);
75 | rootState.setSelected([]);
76 | },
77 |
78 | handleKeyDown(e) {
79 | this.keydown = (e.keyCode || e.which) === this.keyCode;
80 | },
81 |
82 | handleKeyUp() {
83 | this.keydown = false;
84 | },
85 | };
86 |
87 | export default {
88 | config: clickItemBehavior,
89 | name: 'click-item'
90 | };
91 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/behaviors/double-finger.ts:
--------------------------------------------------------------------------------
1 | import { Behavior } from '../interfaces';
2 |
3 | export interface DoubleFingerDragCanvas extends Behavior {
4 | onWheel(ev: any): void;
5 | }
6 |
7 | const doubleFingerDragCanvas: DoubleFingerDragCanvas = {
8 | getEvents() {
9 | return {
10 | wheel: 'onWheel'
11 | };
12 | },
13 | onWheel(ev) {
14 | if (ev.ctrlKey) {
15 | const canvas = this.graph!.get('canvas');
16 | const pixelRatio = canvas.get('pixelRatio') || 1;
17 | const point = canvas.getPointByClient(ev.clientX, ev.clientY);
18 | let ratio = this.graph!.getZoom();
19 | if (ev.wheelDelta > 0) {
20 | ratio += ratio * 0.05;
21 | } else {
22 | ratio -= ratio * 0.05;
23 | }
24 | this.graph!.zoomTo(ratio, {
25 | x: point.x / pixelRatio,
26 | y: point.y / pixelRatio,
27 | });
28 | } else {
29 | const x = ev.deltaX || ev.movementX;
30 | const y = ev.deltaY || ev.movementY;
31 | this.graph!.translate(-x, -y);
32 | }
33 | ev.preventDefault();
34 | }
35 | };
36 |
37 | export default {
38 | config: doubleFingerDragCanvas,
39 | name: 'double-finger-drag-canvas'
40 | }
41 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/behaviors/hover-item.ts:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash';
2 | import { Behavior } from '../interfaces';
3 | import * as G6 from '@antv/g6';
4 | import { rootState } from '../../items/state';
5 |
6 | export interface HoverItemBehavior extends Behavior {
7 | /** 处理鼠标进入 */
8 | handleItemMouseenter({ item }: { item: G6.Item }): void;
9 | /** 处理鼠标移出 */
10 | handleItemMouseleave({ item }: { item: G6.Item }): void;
11 | }
12 |
13 | const hoverItemBehavior: HoverItemBehavior = {
14 | getEvents() {
15 | return {
16 | 'node:mouseenter': 'handleItemMouseenter',
17 | 'edge:mouseenter': 'handleItemMouseenter',
18 | 'node:mouseleave': 'handleItemMouseleave',
19 | 'edge:mouseleave': 'handleItemMouseleave',
20 | };
21 | },
22 |
23 | handleItemMouseenter(e) {
24 | const eleName = get(e, 'target.attrs.name');
25 | if (eleName === 'collapse') return;
26 | const { item } = e;
27 | rootState.setHovered(item.get('id'));
28 | },
29 |
30 | handleItemMouseleave() {
31 | rootState.setHovered('');
32 | },
33 | };
34 |
35 | export default {
36 | config: hoverItemBehavior,
37 | name: 'hover-item'
38 | }
39 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/behaviors/index.ts:
--------------------------------------------------------------------------------
1 | import { default as clickItem } from './click-item';
2 | import { default as doubleFinger } from './double-finger';
3 | import { default as hoverItem } from './hover-item';
4 |
5 | export default [clickItem, hoverItem, doubleFinger];
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/initGraph.tsx:
--------------------------------------------------------------------------------
1 | import G6, { GraphOptions } from '@antv/g6';
2 | import { MixedTreeLayout } from './layouts';
3 | import g6Designer from '../designer';
4 | import { get } from 'lodash';
5 |
6 | export const initTreeGraph = (container: HTMLElement, graphData: Function) => {
7 | // 适应画布
8 | const getContainerSize = () => {
9 | const leftPanel = document.querySelector('.lc-left-area')?.clientWidth || 0;
10 | const rightPanel = document.querySelector('.lc-right-area')?.clientWidth || 0;
11 | return {
12 | width: document.body.offsetWidth - leftPanel - rightPanel,
13 | height: (document.querySelector('.lc-main-area')?.clientHeight || 0),
14 | };
15 | };
16 | const treeLayout = new MixedTreeLayout(g6Designer.layoutConfig);
17 |
18 | // @ts-ignore
19 | const defaultOption = {
20 | container,
21 | fitCenter: true,
22 | fitView: true,
23 | linkCenter: true,
24 | animate: false,
25 | groupByTypes: true,
26 | modes: {
27 | default: [
28 | 'drag-canvas',
29 | 'click',
30 | 'click-item',
31 | 'hover-item',
32 | 'double-finger-drag-canvas',
33 | {
34 | type: 'collapse-expand',
35 | trigger: 'click',
36 | shouldBegin: (e) => {
37 | const eleName = get(e, 'target.attrs.name');
38 | if (eleName === 'collapse') return true;
39 | return false;
40 | },
41 | },
42 | ],
43 | },
44 | defaultEdge: {
45 | type: 'trident',
46 | },
47 | defaultNode: {
48 | type: 'demo-shape'
49 | },
50 | layout: (data: any) => {
51 | const layoutData = treeLayout.layout(data);
52 | return layoutData;
53 | },
54 | width: getContainerSize().width || 500,
55 | height: getContainerSize().height || 500,
56 | } as GraphOptions;
57 |
58 | const treeGraph = new G6.TreeGraph({
59 | ...defaultOption,
60 | ...g6Designer.graphConfig
61 | });
62 | return treeGraph;
63 | }
64 |
65 | export const initGraph = (container: HTMLElement) => {
66 | // 适应画布
67 | const getContainerSize = () => {
68 | const leftPanel = document.querySelector('.lc-left-area')?.clientWidth || 0;
69 | const rightPanel = document.querySelector('.lc-right-area')?.clientWidth || 0;
70 | return {
71 | width: document.body.offsetWidth - leftPanel - rightPanel,
72 | height: document.querySelector('.lc-main-area')?.clientHeight || 0,
73 | };
74 | };
75 | const defaultOption = {
76 | container,
77 | fitCenter: true,
78 | linkCenter: true,
79 | animate: false,
80 | groupByTypes: true,
81 | modes: {
82 | default: [
83 | 'drag-canvas',
84 | 'click',
85 | 'click-item',
86 | 'hover-item',
87 | 'double-finger-drag-canvas',
88 | // 'collapse-expand'
89 | ],
90 | },
91 | defaultEdge: {
92 | type: 'trident',
93 | },
94 | defaultNode: {
95 | type: 'demo-shape'
96 | },
97 | width: getContainerSize().width || 500,
98 | height: getContainerSize().height || 500,
99 | } as GraphOptions;
100 |
101 | const graph = new G6.Graph({
102 | ...defaultOption,
103 | ...g6Designer.graphConfig
104 | });
105 |
106 | return graph;
107 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/layouts/layout/customLayout.js:
--------------------------------------------------------------------------------
1 | import TreeLayout from "@antv/hierarchy/lib/layout/base";
2 | import util from '@antv/hierarchy/lib/util';
3 |
4 | function getBBoxTopDistance(root) {
5 | if (!root) return 0;
6 | if (root.height === root.totalHeight) {
7 | return 0;
8 | }
9 | let startY = 0;
10 | root.eachNode(node => {
11 | if (node.y < startY) {
12 | startY = node.y;
13 | }
14 | });
15 | return root.y - startY;
16 | }
17 |
18 | function getBBoxBottomDistance(root) {
19 | if (!root) return 0;
20 | if (root.height === root.totalHeight) {
21 | return 0;
22 | }
23 | let endY = 0;
24 | root.eachNode(node => {
25 | if (node.y + node.height > endY) {
26 | endY = node.y + node.height;
27 | }
28 | });
29 | return endY - root.y;
30 | }
31 |
32 |
33 | class CompactBoxTreeLayout extends TreeLayout {
34 | execute() {
35 | const me = this;
36 | this.rootNode.children.forEach((item, index) => {
37 | if (index !== 0) {
38 | item.x = me.rootNode.children[0].x;
39 | const preNode = me.rootNode.children[index - 1];
40 | const preNodeSubLayout = preNode.data.subLayout;
41 | const itemSubLayout = item.data.subLayout;
42 | item.y = preNode.y + preNodeSubLayout && getBBoxBottomDistance(preNodeSubLayout) || preNode.height + getBBoxTopDistance(itemSubLayout);
43 | }
44 | });
45 | return this.rootNode;
46 | }
47 | }
48 |
49 | const DEFAULT_OPTIONS = {
50 | };
51 |
52 | function customLayout(root, options) {
53 | options = util.assign({}, DEFAULT_OPTIONS, options);
54 | return new CompactBoxTreeLayout(root, options).execute();
55 | }
56 |
57 | export default customLayout;
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/layouts/layout/customTreeGrid.js:
--------------------------------------------------------------------------------
1 | import TreeLayout from "@antv/hierarchy/lib/layout/base";
2 | import util from '@antv/hierarchy/lib/util';
3 |
4 |
5 | function transTree(root, callback) {
6 | let nodes = [root];
7 | let current;
8 | while (current = nodes.shift()) {
9 | callback(current);
10 | if (current.children) {
11 | nodes = nodes.concat(current.children);
12 | }
13 | }
14 | }
15 |
16 | class CustomTreeGrid extends TreeLayout {
17 | execute() {
18 | // 树转换成二维数组
19 | const me = this;
20 | const tMap = {};
21 | transTree(this.rootNode, (node) => {
22 | if (tMap[node.depth]) {
23 | tMap[node.depth].push(node);
24 | } else {
25 | tMap[node.depth] = [node];
26 | }
27 | node.depth = node.data.depth || node.depth;
28 | });
29 | Object.keys(tMap).forEach(i => {
30 | // 按行
31 | if (i > 0) {
32 | tMap[i].forEach((j, index) => {
33 | // 按列
34 | if (index > 0) {
35 | const preNode = tMap[i][index - 1];
36 | j.x = preNode.x + preNode.width + me.options.vGap;
37 | j.y = preNode.y || 0;
38 | j.lineMaxHeight = j.height > (preNode.lineMaxHeight || preNode.height || 0) ? j.height : (preNode.lineMaxHeight || preNode.height || 0);
39 | } else {
40 | const preLine = tMap[i - 1];
41 | j.y = (preLine[0].y || 0) + (preLine[preLine.length - 1].lineMaxHeight || preLine[preLine.length - 1].height || 0) + me.options.hGap;
42 | j.lineMaxHeight = j.height;
43 | }
44 | });
45 | const lastNode = tMap[i][tMap[i].length - 1];
46 | const lastNodeRight = lastNode.x + lastNode.width;
47 | tMap[i].forEach((j, index) => {
48 | // 每个节点都带上本行最大高度
49 | if (j.data) {
50 | j.data.lineMaxHeight = lastNode.lineMaxHeight - 2 * lastNode.vgap; // 不包含gap的最大高度
51 | }
52 | // 居中处理
53 | j.x = j.x - (lastNodeRight - this.rootNode.width)/2;
54 | });
55 | }
56 | });
57 | this.rootNode.hasAlign = true;
58 | return this.rootNode;
59 | }
60 | }
61 |
62 |
63 | const DEFAULT_OPTIONS = {
64 | vGap: 20,
65 | hGap: 20,
66 | };
67 |
68 | function customLayout(root, options) {
69 | options = util.assign({}, DEFAULT_OPTIONS, options);
70 | return new CustomTreeGrid(root, options).execute();
71 | }
72 |
73 | export default customLayout;
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/layouts/layout/indented.ts:
--------------------------------------------------------------------------------
1 | import TreeLayout from '@antv/hierarchy/lib/layout/base';
2 |
3 | const DEFAULT_INDENT = 20;
4 | /* 纵向布局 */
5 | function positionNode(node: any, previousNode: any, parent: any, dx: number) {
6 | let externalGap = 0;
7 | if (
8 | previousNode // 存在上个节点
9 | && previousNode.data
10 | && (previousNode.data.children && previousNode.data.children.length > 0)
11 | ) {
12 | externalGap = 15;
13 | }
14 | // 控制left offset,要与edges.js里的source anchor匹配
15 | if (parent) {
16 | if (parent.width < 100) {
17 | node.leftOffset = node.width / 2 + parent.leftOffset;
18 | } else {
19 | node.leftOffset = node.width / 2 + parent.leftOffset - parent.width / 2 + 50;
20 | }
21 | } else {
22 | node.leftOffset = node.width / 2;
23 | }
24 | node.x += dx * node.depth + node.leftOffset;
25 | node.y = (previousNode ? previousNode.y + previousNode.height / 2 : 0)
26 | + node.height / 2 + externalGap;
27 | }
28 |
29 | const indentedTree = (root: any, indent = DEFAULT_INDENT) => {
30 | let previousNode: any = null;
31 | root.eachNode((node: any) => {
32 | positionNode(node, previousNode, node.parent, indent);
33 | previousNode = node;
34 | });
35 | };
36 |
37 | const VALID_DIRECTIONS = [
38 | 'LR', // left to right
39 | 'RL', // right to left
40 | ];
41 | const DEFAULT_DIRECTION = VALID_DIRECTIONS[0];
42 |
43 | class IndentedLayout extends TreeLayout {
44 | execute() {
45 | const me: any = this;
46 | const options = me.options;
47 | const root = me.rootNode;
48 | options.isHorizontal = true;
49 | const indent = options.indent;
50 | const direction = options.direction || DEFAULT_DIRECTION;
51 | if (direction && VALID_DIRECTIONS.indexOf(direction) === -1) {
52 | throw new TypeError(`Invalid direction: ${direction}`);
53 | }
54 | if (direction === VALID_DIRECTIONS[0]) { // LR
55 | indentedTree(root, indent);
56 | } else if (direction === VALID_DIRECTIONS[1]) { // RL
57 | indentedTree(root, indent);
58 | root.right2left();
59 | }
60 | return root;
61 | }
62 | }
63 |
64 | const DEFAULT_OPTIONS = {
65 | };
66 |
67 | function indentedLayout(root: any, options: any) {
68 | options = Object.assign({}, DEFAULT_OPTIONS, options);
69 | // @ts-ignore
70 | return new IndentedLayout(root, options).execute();
71 | }
72 |
73 | export default indentedLayout;
74 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/graph/shapes/default-shape.ts:
--------------------------------------------------------------------------------
1 | const defaultShape = {
2 | config: {
3 | draw(model: any, group: any) {
4 | const { size = [300, 180] } = model || {};
5 | const keyShape = group.addShape('rect', {
6 | attrs: {
7 | width: size[0] - 8, // 内部有margin10
8 | height: size[1] - 8,
9 | name: 'key-shape',
10 | fill: '#fff',
11 | radius: 6
12 | },
13 | className: 'wrapper-border',
14 | });
15 | const gRenderer = (window as any).gRendererMaps[model.id];
16 | gRenderer && gRenderer.render(group);
17 | group.sort();
18 |
19 | return keyShape;
20 | },
21 |
22 | update(model: any, item: any) {
23 | const gRenderer = (window as any).gRendererMaps[model.id];
24 | const group = item.getContainer();
25 | gRenderer && gRenderer.update(group);
26 | const wrapper = group.findByClassName('wrapper-border');
27 | wrapper && wrapper.attr({
28 | width: model.width,
29 | height: model.height
30 | });
31 | }
32 | },
33 | name: 'default-shape',
34 | };
35 | export default defaultShape;
36 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/index.ts:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext, project } from '@alilc/lowcode-engine';
2 | import DesignerView from './DesignerView';
3 | // import MaterialPane from './MaterialPane';
4 | import { rootState } from './items/state';
5 | import g6Designer, { G6Designer } from './designer';
6 |
7 | export interface IOptions {
8 | type: 'tree' | 'graph',
9 | graphConfig: any,
10 | layoutConfig: any
11 | }
12 |
13 | /**
14 | * plugin G6 designer
15 | * @param ctx
16 | * @returns
17 | */
18 | const PluginG6Designer = (
19 | ctx: ILowCodePluginContext,
20 | options: IOptions = {
21 | type: 'tree',
22 | graphConfig: {},
23 | layoutConfig: {}
24 | }) => {
25 | return {
26 | exports() {
27 | return g6Designer;
28 | },
29 | init() {
30 | const { skeleton, project } = ctx;
31 | skeleton.remove({
32 | name: 'designer',
33 | area: 'mainArea',
34 | type: 'Widget'
35 | });
36 | skeleton.add({
37 | area: 'mainArea',
38 | name: 'designer',
39 | type: 'Widget',
40 | content: DesignerView,
41 | contentProps: {
42 | ctx,
43 | ...options
44 | }
45 | });
46 | // bind nodes state
47 | rootState.init(project.currentDocument, options.type);
48 | project.onChangeDocument((doc) => {
49 | rootState.disposeDocumentEvent();
50 | rootState.init(project.currentDocument, options.type);
51 | });
52 | }
53 | }
54 | }
55 |
56 | PluginG6Designer.pluginName = 'plugin-g6-designer';
57 | PluginG6Designer.meta = {
58 | preferenceDeclaration: {
59 | title: 'g6图类型',
60 | properties: [
61 | {
62 | key: 'type',
63 | type: 'string',
64 | description: '用户自定义g6图类型',
65 | },
66 | {
67 | key: 'graphConfig',
68 | type: 'any',
69 | description: '图配置'
70 | },
71 | {
72 | key: 'layoutConfig',
73 | type: 'any',
74 | description: '布局配置'
75 | }
76 | ]
77 | }
78 | };
79 | export default PluginG6Designer;
80 | export { G6Designer };
81 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/items/edge/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Graph, TreeGraph, ModelConfig } from '@antv/g6';
3 | import { project } from '@alilc/lowcode-engine';
4 | import { Node } from '@alilc/lowcode-engine';
5 | export interface IModelConfig extends ModelConfig {
6 | id: string;
7 | [key: string]: any;
8 | }
9 | interface Props {
10 | onMountEdge: (edge: Node) => void;
11 | onUnMountEdge: (edge: any) => void;
12 |
13 | graph: Graph | TreeGraph;
14 | lceNode: Node;
15 | }
16 |
17 | class EdgeComponent extends React.PureComponent {
18 |
19 | componentDidMount() {
20 | const { lceNode } = this.props;
21 |
22 | // 收集 edge 统一添加到画布
23 | this.props.onMountEdge(lceNode);
24 |
25 | // set view state
26 | this.setViewState();
27 |
28 | // model 更新渲染
29 | project.currentDocument?.onChangeNodeProp(({ key, oldValue, newValue, node }) => {
30 |
31 | });
32 | }
33 |
34 | componentDidUpdate(prevProps: Props) {
35 | }
36 |
37 | setViewState() {
38 | }
39 |
40 | componentWillUnmount() {
41 | // 删除节点
42 | this.props.onUnMountEdge(this.props.lceNode.propsData);
43 | }
44 |
45 | render() {
46 | return null;
47 | }
48 |
49 | }
50 |
51 | export default EdgeComponent;
52 |
53 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/items/index.less:
--------------------------------------------------------------------------------
1 | .x6-widget-selection-box {
2 | z-index: -10;
3 | }
4 |
5 | .x6-widget-stencil-content {
6 | border: 1px solid #EDEDED;
7 | box-shadow: 0 4px 15px 0 rgb(31 56 88 / 15%);
8 | border-radius: 4px;
9 | background-color: #FFFFFF;
10 | }
11 |
12 | .next-overlay-wrapper .next-balloon {
13 | padding: 10px 16px;
14 | }
15 |
16 | .x6-port-body {
17 | &:hover {
18 | color: #4C6079;
19 | transform: scale(1.3);
20 | }
21 | }
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/items/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { observer } from 'mobx-react';
3 | import NodeComponent, { IModelConfig } from './node';
4 | import EdgeComponent from './edge';
5 | import { Graph, TreeGraph } from '@antv/g6';
6 | import { EdgeComponentName, rootState, transTree } from "./state";
7 | import { Node } from '@alilc/lowcode-engine';
8 | import { merge } from 'lodash';
9 |
10 | import './index.less';
11 |
12 |
13 |
14 | interface Props {
15 | graph: Graph | TreeGraph;
16 | }
17 |
18 | /**
19 | * 渲染节点 & 线条组件
20 | * 响应数据: nodes
21 | */
22 | @observer
23 | class Items extends React.PureComponent {
24 | constructor(props: any) {
25 | super(props);
26 | (window as any).gRendererMaps = {};
27 | }
28 |
29 | tree: any = {};
30 | mounted: boolean = false; // 是否 didMounted
31 |
32 | componentDidMount() {
33 | const { graph } = this.props;
34 | const data = rootState.getGraphData();
35 | graph.data(data);
36 | graph.paint();
37 | graph.render();
38 | graph.fitView();
39 | this.mounted = true;
40 | }
41 |
42 | onMountNode = (node: Node) => {
43 | const { graph } = this.props;
44 | if (this.mounted) {
45 | if (rootState.type === 'tree') {
46 | const t: any = node.parent!.schema.children;
47 | const newChildren = t.map((i: any) => {
48 | transTree(i, (j: any) => {
49 | merge(j, j.props);
50 | });
51 | return i;
52 | });
53 | (graph as TreeGraph).updateChildren(newChildren, node.parent!.id);
54 | // (graph as TreeGraph).addChild(merge(node.propsData, node.propsData!.nodeLayout, { id: node.id }), node.parent!.id);
55 | rootState.setSelected([node.id]);
56 | }
57 | }
58 | }
59 |
60 | onUnMountNode = (nodeId: string) => {
61 | const { graph } = this.props;
62 | if (this.mounted) {
63 | if (rootState.type === 'tree') {
64 | (graph as TreeGraph).removeChild(nodeId);
65 | }
66 | }
67 | }
68 |
69 | onMountEdge = (edge: Node) => {
70 | const { graph } = this.props;
71 | if (this.mounted) {
72 | graph.addItem('edge', edge.propsData);
73 | }
74 | }
75 |
76 | onUnMountEdge = (edge: IModelConfig) => {
77 | const { graph } = this.props;
78 |
79 | if (this.mounted) {
80 | graph.removeItem(edge.id);
81 | }
82 | }
83 |
84 | render() {
85 | const { graph } = this.props;
86 | return (
87 |
88 | {
89 | rootState.nodes.map(item => {
90 | const curId = item.id;
91 | if (item.componentName === EdgeComponentName) {
92 | return (
93 |
100 | )
101 | } else {
102 | return (
103 | )
114 | }
115 | })
116 | }
117 | {/* */}
118 |
119 | )
120 | }
121 | }
122 |
123 | export default Items;
124 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/items/node/index.tsx:
--------------------------------------------------------------------------------
1 | import { Graph, TreeGraph, ModelConfig } from '@antv/g6';
2 | import React from 'react';
3 | import { Node as LceNode } from '@alilc/lowcode-engine';
4 | import { ItemState } from '@alilc/lce-graph-tools';
5 | import { calcLayout } from '../../graph/utils';
6 |
7 | export interface IModelConfig extends ModelConfig {
8 | id: string;
9 | [key: string]: any;
10 | }
11 |
12 | interface Props {
13 | onMountNode: (node: any) => void;
14 | onUnMountNode: (nodeId: string) => void;
15 |
16 | graph: Graph | TreeGraph;
17 | lceNode: LceNode;
18 |
19 | selected: boolean;
20 | hovered: boolean;
21 | error: boolean;
22 | changeFlag: number | undefined;
23 | }
24 |
25 | /**
26 | * node component for x6 node render
27 | */
28 | class NodeComponent extends React.PureComponent {
29 | componentDidMount() {
30 | // 添加节点
31 | const { lceNode, selected, hovered, error } = this.props;
32 | // 计算element canvas定位
33 | const node = calcLayout(lceNode, selected, hovered, error);
34 | if (node) {
35 | const { width, height } = node.box;
36 | lceNode.setPropValue('height', height);
37 | lceNode.setPropValue('width', width);
38 | lceNode.setPropValue('size', [width, height]);
39 | }
40 | // 注册节点config
41 | this.props.onMountNode(lceNode);
42 | }
43 |
44 | componentDidUpdate(prevProps: Props) {
45 | const { changeFlag, selected, hovered, error } = this.props;
46 | if (prevProps.changeFlag !== changeFlag || selected !== prevProps.selected || hovered !== prevProps.hovered || error !== prevProps.error) {
47 | this.updateNode();
48 | }
49 | if (selected !== prevProps.selected || hovered !== prevProps.hovered || error !== prevProps.error) {
50 | this.setNodeState();
51 | }
52 | }
53 |
54 | updateNode() {
55 | const { lceNode, graph, selected, hovered, error } = this.props;
56 | const nodeLayout = calcLayout(lceNode, selected, hovered, error);
57 | if (nodeLayout) {
58 | const oldWidth = lceNode.getPropValue('width');
59 | const oldHeight = lceNode.getPropValue('height');
60 | const { width, height } = nodeLayout.box;
61 | lceNode.setPropValue('height', height);
62 | lceNode.setPropValue('width', width);
63 | lceNode.setPropValue('size', [width, height]);
64 | graph.updateItem(lceNode.id, {
65 | ...lceNode.propsData,
66 | height,
67 | width,
68 | size: [width, height],
69 | });
70 | if (Math.abs(+oldWidth - +width) > 1 || Math.abs(+oldHeight - +height) > 1) {
71 | graph.layout();
72 | }
73 | } else {
74 | graph.updateItem(lceNode.id, {
75 | ...lceNode.propsData
76 | });
77 | graph.layout();
78 | }
79 | }
80 |
81 | setNodeState() {
82 | const { lceNode, graph, selected, hovered, error } = this.props;
83 | const currentNode = graph.findById(lceNode.id);
84 | graph!.setItemState(currentNode, ItemState.Selected, selected);
85 | graph!.setItemState(currentNode, ItemState.Active, hovered);
86 | }
87 |
88 | componentWillUnmount() {
89 | // 删除节点
90 | const { lceNode } = this.props;
91 | this.props.onUnMountNode(lceNode.id);
92 | }
93 |
94 | render() {
95 | return null;
96 | }
97 | }
98 |
99 | export default NodeComponent;
100 |
101 |
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/src/items/state.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, makeObservable } from "mobx";
2 | import { DocumentModel } from '@alilc/lowcode-shell';
3 | import { merge } from 'lodash';
4 |
5 | export function transTree(node: any, callback: Function, parent?: any, depth = 0) {
6 | let flag = callback(node, parent, depth);
7 | if (flag != null) {
8 | return flag;
9 | } else {
10 | if (node.children && node.children.length) {
11 | for (let i = 0; i < node.children.length; i++) {
12 | const child = node.children[i];
13 | flag = flag || transTree(child, callback, node, depth + 1);
14 | if (flag != null) {
15 | return flag;
16 | }
17 | }
18 | }
19 | }
20 | return null;
21 | }
22 | export class RootState {
23 | @observable selected: string[] = []; // select 态,节点 蓝色背景 & 蓝色框
24 | @observable hovered: string = ''; // hover 态,节点蓝色框
25 | @observable error: string[] = []; // error 态,节点红框
26 | @observable nodes: any[] = []; // 单独存一份 nodes 状态数据
27 | document: DocumentModel | null;
28 | type: 'graph' | 'tree' = 'tree';
29 |
30 | documentEvent: (Function | void)[] = [];
31 |
32 | constructor() {
33 | makeObservable(this);
34 | }
35 |
36 | @action
37 | setNodes(nodes: any[]) {
38 | this.nodes = nodes;
39 | }
40 |
41 | getGraphData() {
42 | if (this.type === 'tree') {
43 | // 树布局
44 | const tree = this.document?.root?.exportSchema();
45 | const availableTree = Array.isArray(tree?.children) && tree?.children[0] || {};
46 | transTree(availableTree, (node: any) => {
47 | merge(node, node.props);
48 | });
49 | return availableTree;
50 | } else {
51 | // 图布局
52 | }
53 | }
54 |
55 | disposeDocumentEvent() {
56 | this.documentEvent.forEach(d => d && d());
57 | this.documentEvent = [];
58 | }
59 |
60 | init(document: DocumentModel | null, type: 'graph' | 'tree') {
61 | this.type = type;
62 | if (document) {
63 | this.document = document;
64 | this.setNodes(Array.from(document.nodesMap.values() || []).filter(node => node.componentName !== 'Page'));
65 | this.documentEvent = [
66 | document.onImportSchema(() => {
67 | this.setNodes(Array.from(document.nodesMap.values() || []).filter(node => node.componentName !== 'Page'));
68 | }),
69 | document.onMountNode((node: { node: Node }) => {
70 | this.setNodes(this.nodes.concat(node.node));
71 | }),
72 | document.onRemoveNode((node) => {
73 | const { prevSibling, nextSibling, parent } = node;
74 | const nodes = this.nodes.filter(v => v.id !== node.id);
75 | this.setNodes(nodes);
76 | // 支持连续删除节点
77 | setTimeout(() => {
78 | if (prevSibling) {
79 | this.setSelected([prevSibling.id]);
80 | } else if (nextSibling) {
81 | this.setSelected([nextSibling.id]);
82 | } else if (parent) {
83 | this.setSelected([parent.id]);
84 | }
85 | }, 100);
86 | }),
87 | document.onChangeNodeProp((data: any) => {
88 | const { node, key } = data;
89 | // 节点属性被更新时,判断时应用中间产物
90 | if (!['height', 'width', 'size', 'element'].includes(key)) {
91 | const { id } = node;
92 | this.nodes.map((item: any) => {
93 | if (item.id === id) {
94 | item.propsData.changeFlag = Math.random();
95 | }
96 | return item;
97 | });
98 | };
99 | }),
100 | // 撤销恢复不会触发 onAddNode 和 onRemoveNode 事件
101 | document.history.onChangeState((...data: any) => {
102 | this.setNodes(Array.from(document.nodesMap.values() || []).filter(node => node.componentName !== 'Page'));
103 | }),
104 | ];
105 | }
106 | }
107 |
108 | @action
109 | setSelected(ids: string[]) {
110 | this.selected = ids;
111 | this.document?.selection.selectAll(ids);
112 | }
113 |
114 | @action
115 | setHovered(id: string) {
116 | this.hovered = id;
117 | this.document?.detecting.capture(id);
118 | }
119 |
120 | @action
121 | setError(ids: string[]) {
122 | this.error = ids;
123 | }
124 |
125 | }
126 |
127 | export const rootState = new RootState();
--------------------------------------------------------------------------------
/packages/plugin-g6-designer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/README.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ---
3 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-materials-pane",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch"
15 | },
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public",
19 | "registry": "https://registry.npmjs.org/"
20 | },
21 | "devDependencies": {
22 | "@alib/build-scripts": "^0.1.18",
23 | "build-plugin-component": "^1.10.0"
24 | },
25 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
26 | }
27 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/icon.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import IconBase from '@ali/vu-icon-base';
3 |
4 | export default function Icon() {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { IPublicModelPluginContext } from "@alilc/lowcode-types";
3 | import Pane from "./pane";
4 |
5 | const Icon =
6 | const PluginMaterialsPane = (ctx: IPublicModelPluginContext) => {
7 | return {
8 | // 插件名,注册环境下唯一
9 | name: "LclaActionsPane",
10 | // 依赖的插件(插件名数组)
11 | dep: [],
12 | // 插件对外暴露的数据和方法
13 | exports() {
14 | return {
15 | data: "你可以把插件的数据这样对外暴露",
16 | func: () => {
17 | console.log("方法也是一样");
18 | },
19 | };
20 | },
21 | // 插件的初始化函数,在引擎初始化之后会立刻调用
22 | init() {
23 | // material.setAssets(AssetsJson);
24 | // 你可以拿到其他插件暴露的方法和属性
25 | // const { data, func } = ctx.plugins.pluginA;
26 | // func();
27 | ctx.skeleton.add({
28 | name: "logicActionPane",
29 | area: "leftArea",
30 | type: "PanelDock",
31 | content: ,
32 | props: {
33 | align: 'left',
34 | icon: Icon,
35 | description: "动作库",
36 | },
37 | panelProps: {
38 | area: 'leftFixedArea',
39 | floatable: true,
40 | hideTitleBar: false,
41 | title: "动作库",
42 | width: 192,
43 | },
44 | });
45 |
46 | ctx.skeleton.showPanel('logicActionPane');
47 | },
48 | };
49 | };
50 |
51 | PluginMaterialsPane.pluginName = 'plugin-materials-pane';
52 |
53 | export default PluginMaterialsPane;
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/Icon/Component.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | export default function SvgComponent({ className }) {
4 | return (
5 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/Icon/component.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/Icon/icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/Icon/index.module.scss:
--------------------------------------------------------------------------------
1 | .icon {
2 | display: inline-block;
3 |
4 | width: 20px;
5 | height: 20px;
6 |
7 | > svg {
8 | height: 100%;
9 | width: 100%;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/Icon/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cls from 'classnames/bind';
3 | import Svg from './icon.svg';
4 | import style from './index.module.scss';
5 |
6 | const cx = cls.bind(style);
7 |
8 | export default function Icon() {
9 | return (
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Category/index.module.scss:
--------------------------------------------------------------------------------
1 | .category {
2 | // padding: 0 8px;
3 |
4 | scrollbar-width: none; /* firefox */
5 | -ms-overflow-style: none; /* IE 10+ */
6 |
7 | &::-webkit-scrollbar {
8 | display: none; /* Chrome Safari */
9 | }
10 |
11 | .header {
12 | height: 32px;
13 | display: flex;
14 | flex-direction: row;
15 | align-items: center;
16 | border-top: 1px solid #eaeef2;
17 | }
18 |
19 | .title {
20 | flex: 1;
21 | font-size: 12px;
22 | color: rgba(0,0,0,0.8);
23 | }
24 |
25 | .icon {
26 | width: 32px;
27 | flex-grow: 0;
28 | flex-shrink: 0;
29 | text-align: center;
30 | cursor: pointer;
31 | transition: transform 0.2s ease-in-out;
32 |
33 | &.expand {
34 | transform: rotate(90deg);
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Category/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import cls from "classnames/bind";
3 | import { Animate, Icon } from "@alifd/next";
4 | import style from "./index.module.scss";
5 |
6 | const cx = cls.bind(style);
7 |
8 | interface Props {
9 | name: string;
10 | children?: React.ReactElement;
11 | }
12 |
13 | interface State {
14 | expand: boolean;
15 | }
16 |
17 | export default class Category extends React.Component {
18 | state = {
19 | expand: true,
20 | };
21 |
22 | height = 0;
23 |
24 | handleToggle = () => {
25 | this.setState((state) => {
26 | return {
27 | expand: !state.expand,
28 | };
29 | });
30 | };
31 |
32 | beforeEnter = (node) => {
33 | this.height = node.offsetHeight;
34 | node.style.height = "0px";
35 | };
36 |
37 | onEnter = (node) => {
38 | node.style.height = `${this.height}px`;
39 | };
40 |
41 | afterEnter = (node) => {
42 | this.height = null;
43 | node.style.height = null;
44 | };
45 |
46 | beforeLeave = (node) => {
47 | node.style.height = `${node.offsetHeight}px`;
48 | };
49 |
50 | onLeave = (node) => {
51 | node.style.height = "0px";
52 | };
53 |
54 | afterLeave = (node) => {
55 | node.style.height = null;
56 | };
57 |
58 | render() {
59 | const { children, name } = this.props;
60 | const { expand } = this.state;
61 | return (
62 |
63 |
64 |
65 |
70 |
71 |
{name}
72 |
73 |
82 | {expand ? children : null}
83 |
84 |
85 | );
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Component/index.module.scss:
--------------------------------------------------------------------------------
1 | .card {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: space-between;
6 | padding: 12px 8px 8px;
7 | width: calc(100 / 3 * 0.01);
8 | height: 64px;
9 | flex-grow: 0;
10 | flex-shrink: 0;
11 | border-right: 1px solid #eaeaea;
12 | border-bottom: 1px solid #eaeaea;
13 | box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.15);
14 | transition: box-shadow 0.2s ease;
15 |
16 | &:hover {
17 | box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.15);
18 | border-color: transparent;
19 | }
20 |
21 | &:nth-child(3n) {
22 | border-right: none;
23 | }
24 |
25 | &:nth-child(3n+1):nth-last-child(-n+3),
26 | &:nth-child(3n+1):nth-last-child(-n+3)~& {
27 | border-bottom: none;
28 | }
29 |
30 | .icon {
31 | width: 20px;
32 | height: 20px;
33 | margin: 0 1px;
34 | display: flex;
35 | justify-content: center;
36 | align-items: center;
37 | margin-right: 4px;
38 |
39 | >img {
40 | width: 100%;
41 | }
42 | }
43 |
44 | .name {
45 | width: 48px;
46 | height: 20px;
47 | line-height: 20px;
48 | white-space: nowrap;
49 | text-overflow: ellipsis;
50 | overflow: hidden;
51 | color: rgba(0, 0, 0, 0.6);
52 | text-align: center;
53 | }
54 | }
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Component/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cls from 'classnames/bind';
3 | import Svg from '../../Icon/Component';
4 | import { Balloon } from '@alifd/next';
5 | import style from './index.module.scss';
6 | import { getTextReader, StandardComponentMeta } from '../../utils/transform';
7 |
8 | const cx = cls.bind(style);
9 |
10 | interface Props {
11 | data: any;
12 | }
13 |
14 | interface State {
15 | icon: string | React.ReactNode;
16 | snippet: any;
17 | }
18 |
19 | export default class Component extends React.Component {
20 | static getDerivedStateFromProps(props) {
21 | const { data } = props;
22 | const { icon, snippets = [] } = data;
23 | const snippet = snippets[0];
24 | const screenshot = snippet?.screenshot ?? icon;
25 |
26 | return {
27 | icon: screenshot,
28 | snippet,
29 | };
30 | }
31 |
32 | state = {
33 | icon: '',
34 | snippet: null,
35 | };
36 |
37 | t: (s) => string;
38 |
39 | constructor(props) {
40 | super(props);
41 | this.t = getTextReader('zh_CN');
42 | }
43 |
44 | renderIcon() {
45 | const { icon } = this.state;
46 |
47 | if (!icon) {
48 | return ;
49 | }
50 |
51 | if (typeof icon === 'string') {
52 | return
;
53 | }
54 |
55 | if (typeof icon === 'function') {
56 | const X = icon as any;
57 | return ;
58 | }
59 |
60 | return icon;
61 | }
62 |
63 | render() {
64 | const { data } = this.props;
65 | const { title } = data;
66 | const { snippet } = this.state;
67 |
68 | return (
69 |
70 |
{this.renderIcon()}
71 |
{this.t(title)}
72 |
73 | );
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/DragGhost/index.scss:
--------------------------------------------------------------------------------
1 | .lc-ghost-group {
2 | box-sizing: border-box;
3 | position: fixed;
4 | z-index: 99999;
5 | width: 100px;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | pointer-events: none;
10 | background-color: rgba(0, 0, 0, 0.4);
11 | box-shadow: 0 0 6px grey;
12 | transform: translate(-10%, -50%);
13 | .lc-ghost {
14 | .lc-ghost-title {
15 | text-align: center;
16 | font-size: var(--font-size-text);
17 | text-overflow: ellipsis;
18 | color: var(--color-text-light);
19 | white-space: nowrap;
20 | overflow: hidden;
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/DragGhost/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Title } from '@alilc/lowcode-editor-core';
3 | import { I18nData, NodeSchema } from '@alilc/lowcode-types';
4 | import './index.scss';
5 |
6 | type offBinding = () => any;
7 |
8 | export default class DragGhost extends React.Component {
9 |
10 | private dispose: offBinding[] = [];
11 | private titles: (string | I18nData | React.ReactElement)[] | null = null;
12 | private _dragon = this.props.dragon;
13 | private _material = this.props.material;
14 |
15 | constructor(props: any) {
16 | super(props);
17 | this.state = {
18 | x: 0,
19 | y: 0
20 | }
21 | this.dispose = [
22 | this._dragon.onDragstart(e => {
23 | if (e.originalEvent.type.slice(0, 4) === 'drag') {
24 | return;
25 | }
26 | this.titles = this.getTitles(e.dragObject);
27 | this.setState({
28 | x: e.globalX,
29 | y: e.globalY
30 | })
31 | }),
32 | this._dragon.onDrag(e => {
33 | this.setState({
34 | x: e.globalX,
35 | y: e.globalY
36 | })
37 | }),
38 | this._dragon.onDragend(() => {
39 | this.titles = null;
40 | this.setState({
41 | x: 0,
42 | y: 0
43 | })
44 | }),
45 | ];
46 | }
47 |
48 | getTitles(dragObject: any) {
49 | if (dragObject && dragObject.type === "node") {
50 | return dragObject.nodes.map((node) => node.title);
51 | }
52 |
53 | const dataList = Array.isArray(dragObject.data) ? dragObject.data : [dragObject.data];
54 |
55 | return dataList.map((item: NodeSchema, i) => (this._material.getComponentMeta(item.componentName).title));
56 | }
57 |
58 | componentWillUnmount() {
59 | if (this.dispose) {
60 | this.dispose.forEach(off => off());
61 | }
62 | }
63 |
64 | renderGhostGroup() {
65 | return this.titles?.map((title, i) => {
66 | const ghost = (
67 |
68 |
69 |
70 | );
71 | return ghost;
72 | });
73 | }
74 |
75 | render() {
76 | if (!this.titles || !this.titles.length) {
77 | return null;
78 | }
79 |
80 | return (
81 |
88 | {this.renderGhostGroup()}
89 |
90 | );
91 | }
92 | }
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/List/index.module.scss:
--------------------------------------------------------------------------------
1 | .cards {
2 | display: flex;
3 | flex-wrap: wrap;
4 | border-top: 1px solid #eaeef2;
5 | }
6 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/List/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cls from 'classnames/bind';
3 | import style from './index.module.scss';
4 |
5 | const cx = cls.bind(style);
6 |
7 | interface Props {
8 | className?: string;
9 | children?: React.ReactNode
10 | }
11 |
12 | export default class List extends React.Component {
13 | render() {
14 | const { className } = this.props;
15 | return {this.props.children}
;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Tab/index.module.scss:
--------------------------------------------------------------------------------
1 | .tab {
2 | display: flex;
3 | flex-direction: column;
4 |
5 | .header {
6 | position: relative;
7 | flex-grow: 0;
8 | flex-shrink: 0;
9 |
10 | .items {
11 | display: flex;
12 | height: 32px;
13 | box-shadow: inset 0 -1px 0 0 rgba(0, 0, 0, 0.07);
14 | }
15 |
16 | .item {
17 | padding: 0 16px;
18 | flex-shrink: 0;
19 | flex-grow: 0;
20 | display: flex;
21 | height: 100%;
22 | align-items: center;
23 | justify-content: center;
24 | color: #666;
25 | font-size: 12px;
26 | cursor: pointer;
27 |
28 | &:hover {
29 | color: #000;
30 | }
31 |
32 | &.active {
33 | color: black;
34 | }
35 | }
36 |
37 | .indicator {
38 | content: '';
39 | position: absolute;
40 | bottom: 0;
41 | left: 0;
42 | width: 72px;
43 | height: 1px;
44 | background-color: black;
45 | transition: left 0.3s ease;
46 | }
47 | }
48 |
49 | .tabs {
50 | flex: 1;
51 | overflow: hidden;
52 |
53 | .contents {
54 | height: 100%;
55 | display: flex;
56 | flex-direction: row;
57 | flex-wrap: nowrap;
58 | transform: translate3d(0, 0, 0);
59 | transition: transform 0.3s ease-in-out;
60 | }
61 |
62 | .content {
63 | width: 100%;
64 | height: 100%;
65 | // overflow-y: overlay;
66 | overflow-y: hidden;
67 | overflow-x: hidden;
68 | flex-shrink: 0;
69 | flex-grow: 0;
70 | opacity: 0;
71 | transition: opacity 0.3s ease-in-out;
72 |
73 | // &::-webkit-scrollbar-thumb {
74 | // background-color: #999;
75 | // }
76 |
77 | &.active {
78 | opacity: 1;
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/components/Tab/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import cls from 'classnames/bind';
3 | import style from './index.module.scss';
4 |
5 | const cx = cls.bind(style);
6 |
7 | interface Props {
8 | className?: string;
9 | children?: React.ReactNode;
10 | }
11 |
12 | interface State {
13 | active: number;
14 | offset: number;
15 | }
16 |
17 | export default class Tab extends React.Component {
18 | static Item;
19 |
20 | state: State = {
21 | active: 0,
22 | offset: 0,
23 | };
24 |
25 | menus = React.createRef();
26 |
27 | componentDidMount() {
28 | const offset = this.getIndicatorPos();
29 | // eslint-disable-next-line
30 | this.setState({ offset });
31 | }
32 |
33 | getIndicatorPos = (index?: number) => {
34 | const { active } = this.state;
35 |
36 | index = typeof index === 'undefined' ? active : index;
37 |
38 | const dom = this.menus.current;
39 | if (dom) {
40 | const children = dom.childNodes;
41 |
42 | const target = children[index] as HTMLDivElement;
43 |
44 | if (!target) {
45 | return 0;
46 | }
47 |
48 | return target.offsetLeft;
49 | }
50 | return 0;
51 | };
52 |
53 | format = () => {
54 | const { children } = this.props;
55 | const childs = React.Children.toArray(children);
56 | const menus = [];
57 | for (let i = 0; i < childs.length; i += 1) {
58 | const { props } = childs[i] as React.ReactElement;
59 | menus.push({
60 | title: props?.title,
61 | index: i,
62 | children: props.children,
63 | });
64 | }
65 |
66 | return {
67 | menus,
68 | };
69 | };
70 |
71 | handleSelect = (active) => {
72 | this.setState({
73 | active,
74 | offset: this.getIndicatorPos(active),
75 | });
76 | };
77 |
78 | render() {
79 | const { active, offset } = this.state;
80 | const { className } = this.props;
81 |
82 | const { menus } = this.format();
83 |
84 | return (
85 |
86 |
87 |
88 |
89 | {menus.map((menu) => {
90 | return (
91 |
this.handleSelect(menu.index)}
95 | >
96 | {menu.title}
97 |
98 | );
99 | })}
100 |
101 |
102 |
103 |
109 | {menus.map(({ children, index }) => {
110 | return (
111 |
112 | {children}
113 |
114 | );
115 | })}
116 |
117 |
118 |
119 | );
120 | }
121 | }
122 |
123 | Tab.Item = function TabItem({ children }) {
124 | return children;
125 | };
126 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/index.module.scss:
--------------------------------------------------------------------------------
1 | #design-view {
2 | width: 100%;
3 | }
4 | .lowcode-component-panel {
5 | height: 100%;
6 | overflow: hidden;
7 | border-top: 1px solid transparent;
8 | display: flex;
9 | flex-direction: column;
10 | margin-bottom: 12px;
11 |
12 | ::-webkit-scrollbar {
13 | display: none !important;
14 | }
15 |
16 | > .header {
17 | // margin: 12px 16px;
18 | padding: 0 8px;
19 | flex-shrink: 0;
20 | flex-grow: 0;
21 |
22 | .search {
23 | width: 100%;
24 | }
25 | }
26 |
27 | > .tabs {
28 | flex: 1;
29 | overflow: hidden;
30 | }
31 |
32 | > .empty {
33 | display: flex;
34 | flex: auto;
35 | flex-flow: column nowrap;
36 | justify-content: center;
37 | align-items: center;
38 | img {
39 | width: 100px;
40 | height: 100px;
41 | }
42 | .content {
43 | line-height: 2;
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/src/pane/store/index.ts:
--------------------------------------------------------------------------------
1 | import { SnippetMeta } from '../utils/transform';
2 |
3 | export default class ComponentManager {
4 | snippets = new Map();
5 |
6 | setSnippets = (snippets: SnippetMeta[]) => {
7 | for (const snippet of snippets) {
8 | if (snippet.id) {
9 | this.snippets.set(snippet.id, snippet.schema);
10 | }
11 | }
12 | };
13 |
14 | getSnippetById = (id: string) => {
15 | return this.snippets.get(id);
16 | };
17 | }
18 |
--------------------------------------------------------------------------------
/packages/plugin-materials-pane/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-tools/README.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ---
3 |
--------------------------------------------------------------------------------
/packages/plugin-tools/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-tools/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-tools",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch",
15 | "prepublish": "npm run build"
16 | },
17 | "license": "MIT",
18 | "publishConfig": {
19 | "access": "public",
20 | "registry": "https://registry.npmjs.org/"
21 | },
22 | "devDependencies": {
23 | "@alib/build-scripts": "^0.1.18",
24 | "build-plugin-component": "^1.10.0"
25 | },
26 | "dependencies": {
27 | "@antv/g6": "^4.6.4",
28 | "@antv/x6": "^1.31.4"
29 | },
30 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
31 | }
32 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/common/command.ts:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
2 | import { Graph, TreeGraph } from '@antv/g6';
3 | import { Graph as X6Graph } from '@antv/x6';
4 |
5 | export type IGraph = typeof Graph | typeof TreeGraph | typeof X6Graph;
6 | export type ICommandCb = (ctx: ILowCodePluginContext, graph: IGraph, data: any) => void;
7 |
8 | export interface ICommands {
9 | [key: string]: ICommandCb
10 | }
11 |
12 | export class CommandManager {
13 | constructor(data: ICommands) {
14 | this.commands = data;
15 | }
16 |
17 | ctx: ILowCodePluginContext;
18 |
19 | graph: IGraph;
20 |
21 | commands: ICommands = {};
22 |
23 | init(ctx: ILowCodePluginContext, graph: IGraph) {
24 | this.ctx = ctx;
25 | this.graph = graph;
26 | // TODO: 重复监听
27 | Object.keys(this.commands).forEach((key: string) => {
28 | ctx.event.on(`common:${key}`, (data: any) => {
29 | this.commands[key](ctx, graph, data);
30 | });
31 | });
32 | }
33 |
34 | register(key: string, listener: ICommandCb) {
35 | this.commands[key] = listener;
36 | if (this.ctx && this.graph) {
37 | this.ctx?.event.on(`common:${key}`, (data: any) => {
38 | listener(this.ctx, this.graph, data);
39 | });
40 | }
41 | }
42 |
43 | get(key: string) {
44 | return this.commands[key];
45 | }
46 | }
--------------------------------------------------------------------------------
/packages/plugin-tools/src/common/index.ts:
--------------------------------------------------------------------------------
1 | //
2 | export * from './command';
3 | export * from './constants';
4 | export * from './utils';
--------------------------------------------------------------------------------
/packages/plugin-tools/src/index.scss:
--------------------------------------------------------------------------------
1 | .lc-toolbar {
2 | .lc-toolbar-left {
3 | display: flex;
4 | .tool-item {
5 | cursor: pointer;
6 | width: auto;
7 | margin-right: 10px;
8 | line-height: 20px;
9 | }
10 | }
11 | }
--------------------------------------------------------------------------------
/packages/plugin-tools/src/index.ts:
--------------------------------------------------------------------------------
1 | import './index.scss';
2 | export * from './tools/zoomIn';
3 | export * from './tools/zoomOut';
4 | export * from './tools/insertItem';
5 | export * from './tools/removeItem';
6 | export * from './tools/save';
7 | export * from './tools/logo';
8 | export * from './plugins';
9 | export * from './common';
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/InsertItemPlugin.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine'
3 | import { EditorCommand } from '../common'
4 |
5 | interface IInsertProps {
6 | ctx: ILowCodePluginContext // 上下文
7 | model: any // 节点配置
8 | nodeType: EditorCommand.insertChildNode | EditorCommand.insertSiblingNode
9 | }
10 |
11 | // 插入渲染
12 | class InsertView extends PureComponent {
13 | handleClick = () => {
14 | const { ctx, model, nodeType } = this.props
15 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes()
16 | if (selectedNodes && selectedNodes.length > 0) {
17 | // 有选中节点触发
18 | ctx.event.emit(EditorCommand[nodeType], { selectedNodes, model })
19 | }
20 | }
21 |
22 | render() {
23 | const { nodeType } = this.props
24 | return (
25 |
26 |
27 | {nodeType === EditorCommand.insertChildNode ? '新增子节点' : '新增兄弟节点'}
28 |
29 |
30 | )
31 | }
32 | }
33 |
34 | // 插入节点插件
35 | const InsertItemPlugin = (ctx: ILowCodePluginContext, options: { model: any }) => {
36 | const nodeType = [EditorCommand.insertChildNode, EditorCommand.insertSiblingNode]
37 | return {
38 | name: 'insertItem',
39 | async init() {
40 | const { skeleton } = ctx
41 | nodeType.forEach((item) => {
42 | console.log('alex', item)
43 | skeleton.add({
44 | name: item,
45 | area: 'toolbar',
46 | type: 'Widget',
47 | props: {
48 | align: 'left',
49 | },
50 | content: InsertView,
51 | contentProps: {
52 | ctx,
53 | model: options.model,
54 | nodeType: item,
55 | },
56 | })
57 | hotkey.bind(item === EditorCommand.insertChildNode ? 'tab' : 'enter', (e: any) => {
58 | e.preventDefault()
59 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes()
60 | const { model } = options
61 | if (selectedNodes && selectedNodes.length > 0) {
62 | // 有选中节点触发
63 | ctx.event.emit(item, { selectedNodes, model })
64 | }
65 | })
66 | })
67 | },
68 | }
69 | }
70 |
71 | InsertItemPlugin.pluginName = 'insertItemPlugin'
72 | InsertItemPlugin.meta = {
73 | preferenceDeclaration: {
74 | title: '新增节点配置',
75 | properties: [
76 | {
77 | key: 'model',
78 | type: 'any',
79 | description: '用户自定义新增节点配置',
80 | },
81 | ],
82 | },
83 | }
84 |
85 | export default InsertItemPlugin
86 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/OperateButtonPlugin.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react'
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine'
3 | import { Button } from '@alifd/next'
4 |
5 | interface IOperateButtonProps {
6 | ctx: ILowCodePluginContext // 上下文
7 | callback: (schema: any) => void // 回调函数
8 | text: string // 按钮显示文本
9 | btnProps?: any // 按钮属性
10 | }
11 |
12 | interface IOpratePluginOption {
13 | callback: (schema: any) => void
14 | name: string
15 | text: string
16 | btnProps?: any
17 | hotkey?: string
18 | }
19 |
20 | // 操作按钮
21 | class OperateButton extends PureComponent {
22 | handleClick = () => {
23 | const { ctx, callback } = this.props
24 | const currentSchema = ctx.project.currentDocument?.exportSchema()
25 | callback?.(currentSchema)
26 | }
27 |
28 | render() {
29 | const { text, btnProps } = this.props
30 | return (
31 |
34 | )
35 | }
36 | }
37 |
38 | // 操作按钮插件
39 | const OperateButtonPlugin = (ctx: ILowCodePluginContext, options: IOpratePluginOption | IOpratePluginOption[]) => {
40 | const buttonsArray = Array.isArray(options) ? options : [options]
41 | return {
42 | name: 'operateButton',
43 | async init() {
44 | const { skeleton } = ctx
45 | buttonsArray.forEach((item: IOpratePluginOption) => {
46 | // 添加按钮
47 | skeleton.add({
48 | name: item.name,
49 | area: 'topArea',
50 | type: 'Widget',
51 | props: {
52 | align: 'right',
53 | },
54 | content: OperateButton,
55 | contentProps: {
56 | ctx,
57 | callback: item.callback,
58 | text: item.text,
59 | btnProps: item.btnProps,
60 | },
61 | })
62 | // 添加快捷键
63 | item.hotkey &&
64 | hotkey.bind(item.hotkey, (e: any) => {
65 | e.preventDefault()
66 | const currentSchema = ctx.project.currentDocument?.exportSchema()
67 | item.callback(currentSchema)
68 | })
69 | })
70 | },
71 | }
72 | }
73 |
74 | OperateButtonPlugin.pluginName = 'operateButtonPlugin'
75 | OperateButtonPlugin.meta = {
76 | preferenceDeclaration: {
77 | title: '自定义操作按钮',
78 | properties: [
79 | {
80 | key: 'callback',
81 | type: 'Function',
82 | description: '用户自定义按钮处理',
83 | },
84 | {
85 | key: 'name',
86 | type: 'Function',
87 | description: '用户自定义按钮名',
88 | },
89 | {
90 | key: 'text',
91 | type: 'Function',
92 | description: '用户自定义按钮文本',
93 | },
94 | {
95 | key: 'btnProps',
96 | type: 'Object',
97 | description: '自定义按钮属性设置',
98 | },
99 | {
100 | key: 'hotKey',
101 | type: 'Function',
102 | description: '用户自定义按钮快捷键',
103 | },
104 | ],
105 | },
106 | }
107 |
108 | export default OperateButtonPlugin
109 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/RemoveItemPlugin.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine';
3 | import { isFormEvent } from '@alilc/lowcode-utils';
4 | import { EditorCommand } from '../common';
5 |
6 | interface IRemoveProps {
7 | ctx: ILowCodePluginContext // 上下文
8 | mode: string
9 | }
10 |
11 | // 删除渲染
12 | class RemoveView extends PureComponent {
13 | handleClick = () => {
14 | const { ctx } = this.props
15 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes();
16 | if (selectedNodes && selectedNodes.length > 0) {
17 | // 有选中节点触发
18 | ctx.event.emit(EditorCommand.removeItem, selectedNodes)
19 | }
20 | }
21 |
22 | render() {
23 | return (
24 |
25 |
26 | 删除
27 |
28 |
29 | )
30 | }
31 | }
32 |
33 | // 删除节点插件
34 | const RemoveItemPlugin = (ctx: ILowCodePluginContext) => {
35 | const onRemove = (e: any) => {
36 | if (isFormEvent(e)) return;
37 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes();
38 | if (selectedNodes && selectedNodes.length > 0) {
39 | // 有选中节点触发
40 | ctx.event.emit(EditorCommand.removeItem, selectedNodes);
41 | }
42 | }
43 | return {
44 | name: 'removeItem',
45 | async init() {
46 | const { skeleton } = ctx
47 | skeleton.add({
48 | name: 'removeItem',
49 | area: 'toolbar',
50 | type: 'Widget',
51 | props: {
52 | align: 'left',
53 | },
54 | content: RemoveView,
55 | contentProps: {
56 | ctx,
57 | },
58 | });
59 | hotkey.bind(['backspace', 'del'], (e) => {
60 | onRemove(e);
61 | });
62 | },
63 | }
64 | }
65 |
66 | RemoveItemPlugin.pluginName = 'removeItemPlugin'
67 |
68 | export default RemoveItemPlugin
69 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/UndoRedoPlugin.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { hotkey } from '@alilc/lowcode-engine';
3 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
4 | import { EditorCommand } from '../common';
5 |
6 | interface IUndoRedoProps {
7 | ctx: IPublicModelPluginContext; // 上下文
8 | mode: string;
9 | }
10 |
11 | // 缩放渲染
12 | class UndoRedoView extends PureComponent {
13 | handleClick = () => {
14 | const { ctx, mode } = this.props;
15 | ctx.event.emit(mode === 'undo' ? EditorCommand.Undo : EditorCommand.Redo);
16 | }
17 |
18 | render() {
19 | const { mode } = this.props;
20 | return (
21 |
22 |
23 | {mode === 'undo' ? '撤销' : '恢复'}
24 |
25 |
26 | );
27 | }
28 | }
29 |
30 | // 缩放插件
31 | const UndoRedoPlugin = (ctx: IPublicModelPluginContext) => {
32 | return {
33 | name: 'zoom',
34 | async init() {
35 | const { skeleton } = ctx;
36 | const modes = ['undo', 'redo'];
37 | modes.forEach((item) => {
38 | skeleton.add({
39 | name: item,
40 | area: 'toolbar',
41 | type: 'Widget',
42 | props: {
43 | align: 'left',
44 | },
45 | content: UndoRedoView,
46 | contentProps: {
47 | ctx,
48 | mode: item,
49 | },
50 | })
51 | // 绑定快捷键
52 | const key = item === 'undo' ? 'command+z' : 'command+y';
53 | hotkey.bind(key, (e: any) => {
54 | e.preventDefault()
55 | ctx.event.emit(item === 'undo' ? EditorCommand.Undo : EditorCommand.Redo)
56 | })
57 | })
58 | },
59 | }
60 | }
61 |
62 | UndoRedoPlugin.pluginName = 'undoRedoPlugin';
63 |
64 | export default UndoRedoPlugin;
65 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/ZoomPlugin.tsx:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine';
3 | import { EditorCommand } from '../common';
4 |
5 | interface IZoomProps {
6 | ctx: ILowCodePluginContext; // 上下文
7 | mode: string;
8 | }
9 |
10 | // 缩放渲染
11 | class ZoomView extends PureComponent {
12 | handleClick = () => {
13 | const { ctx, mode } = this.props;
14 | ctx.event.emit(mode === 'zoom-in' ? EditorCommand.ZoomIn : EditorCommand.ZoomOut);
15 | }
16 |
17 | render() {
18 | const { mode } = this.props;
19 | return (
20 |
21 |
22 | {mode === 'zoom-in' ? '放大' : '缩小'}
23 |
24 | {/* */}
25 |
26 | );
27 | }
28 | }
29 |
30 | // 缩放插件
31 | const ZoomPlugin = (ctx: ILowCodePluginContext) => {
32 | return {
33 | name: 'zoom',
34 | async init() {
35 | const { skeleton } = ctx
36 | const modes = ['zoom-in', 'zoom-out']
37 | modes.forEach((item) => {
38 | skeleton.add({
39 | name: item,
40 | area: 'toolbar',
41 | type: 'Widget',
42 | props: {
43 | align: 'left',
44 | },
45 | content: ZoomView,
46 | contentProps: {
47 | ctx,
48 | mode: item,
49 | },
50 | })
51 | // 绑定快捷键
52 | const key = item === 'zoom-in' ? 'command+1' : 'command+2';
53 | hotkey.bind(key, (e: any) => {
54 | e.preventDefault()
55 | ctx.event.emit(item === 'zoom-in' ? EditorCommand.ZoomIn : EditorCommand.ZoomOut)
56 | })
57 | })
58 | },
59 | }
60 | }
61 |
62 | ZoomPlugin.pluginName = 'zoomPlugin';
63 |
64 | export default ZoomPlugin;
65 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | import ZoomPlugin from './ZoomPlugin';
2 | import OperateButtonPlugin from './OperateButtonPlugin';
3 | import RemoveItemPlugin from './RemoveItemPlugin';
4 | import InsertItemPlugin from './InsertItemPlugin';
5 | import UndoRedoPlugin from './UndoRedoPlugin';
6 |
7 | export { ZoomPlugin, OperateButtonPlugin, RemoveItemPlugin, InsertItemPlugin, UndoRedoPlugin }
8 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/tools/logo.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { ILowCodePluginContext } from '@alilc/lowcode-engine'
3 |
4 | class Logo extends React.Component<{ ctx: ILowCodePluginContext; imgUrl?: string; style?: any }, any> {
5 | render() {
6 | const { imgUrl, style } = this.props
7 | return (
8 |
13 | )
14 | }
15 | }
16 |
17 | export const logo = (ctx: ILowCodePluginContext, options: { imgUrl: string; style?: any }) => {
18 | return {
19 | name: 'logo',
20 | async init() {
21 | const { skeleton } = ctx
22 |
23 | skeleton.add({
24 | name: 'logo',
25 | area: 'topArea',
26 | type: 'Widget',
27 | props: {
28 | align: 'left',
29 | },
30 | content: Logo,
31 | contentProps: {
32 | ctx,
33 | ...options,
34 | },
35 | })
36 | },
37 | }
38 | }
39 | logo.pluginName = 'logo'
40 | logo.meta = {
41 | preferenceDeclaration: {
42 | title: 'logo配置',
43 | properties: [
44 | {
45 | key: 'imgUrl',
46 | type: 'string',
47 | description: '用户自定义logo资源',
48 | },
49 | {
50 | key: 'style',
51 | type: 'any',
52 | description: '用户自定义logo样式',
53 | },
54 | ],
55 | },
56 | }
57 |
--------------------------------------------------------------------------------
/packages/plugin-tools/src/tools/removeItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine';
3 | import { isFormEvent } from '@alilc/lowcode-utils';
4 | import { EditorCommand } from '../common';
5 |
6 | class Tool extends React.Component<{ ctx: ILowCodePluginContext }, any> {
7 | handleClick() {
8 | const { ctx } = this.props;
9 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes();
10 | if (selectedNodes && selectedNodes.length > 0) {
11 | // 有选中节点触发
12 | ctx.event.emit(EditorCommand.removeItem, selectedNodes);
13 | }
14 | }
15 | render() {
16 | return (
17 |
18 | 删除节点
19 |
20 | )
21 | }
22 | }
23 |
24 | export const removeItem = (ctx: ILowCodePluginContext) => {
25 | return {
26 | name: 'removeItem',
27 | async init() {
28 | const { skeleton } = ctx;
29 |
30 | skeleton.add({
31 | name: 'removeItem',
32 | area: 'toolbar',
33 | type: 'Widget',
34 | props: {
35 | align: 'left'
36 | },
37 | content: Tool,
38 | contentProps: {
39 | ctx
40 | }
41 | });
42 | },
43 | };
44 | };
45 | removeItem.pluginName = 'removeItem';
46 |
47 | export const removeItemHotKey = (ctx: ILowCodePluginContext, options: any) => {
48 | const onRemove = (e: any) => {
49 | if (isFormEvent(e)) return;
50 | const selectedNodes = ctx.project.currentDocument?.selection.getNodes();
51 | if (selectedNodes && selectedNodes.length > 0) {
52 | // 有选中节点触发
53 | ctx.event.emit(EditorCommand.removeItem, selectedNodes);
54 | }
55 | }
56 | return {
57 | name: 'removeItemHotKey',
58 | async init() {
59 | hotkey.bind(['backspace', 'del'], (e) => {
60 | onRemove(e);
61 | });
62 | }
63 | };
64 | };
65 | removeItemHotKey.pluginName = 'removeItemHotKey';
--------------------------------------------------------------------------------
/packages/plugin-tools/src/tools/save.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine';
3 | import { Button } from '@alifd/next';
4 |
5 | class Save extends React.Component<{ ctx: ILowCodePluginContext, cb: any, type: 'save' | 'publish' }, any> {
6 | handleSave() {
7 | const { ctx, cb } = this.props;
8 | const currentSchema = ctx.project.currentDocument?.exportSchema();
9 | cb(currentSchema);
10 | }
11 | render() {
12 | const { type } = this.props;
13 | return (
14 |
15 | )
16 | }
17 | }
18 |
19 | export const save = (ctx: ILowCodePluginContext, options: any) => {
20 | return {
21 | name: 'save',
22 | async init() {
23 | const { skeleton } = ctx;
24 |
25 | skeleton.add({
26 | name: 'save',
27 | area: 'topArea',
28 | type: 'Widget',
29 | props: {
30 | align: 'right'
31 | },
32 | content: Save,
33 | contentProps: {
34 | ctx,
35 | cb: options.cb,
36 | type: 'save'
37 | }
38 | });
39 | }
40 | };
41 | };
42 | save.pluginName = 'save';
43 | save.meta = {
44 | preferenceDeclaration: {
45 | title: '保存回调',
46 | properties: [{
47 | key: 'cb',
48 | type: 'Function',
49 | description: '用户自定义保存处理',
50 | }]
51 | }
52 | };
53 |
54 | export const saveHotKey = (ctx: ILowCodePluginContext, options: any) => {
55 | return {
56 | name: 'saveHotKey',
57 | async init() {
58 | hotkey.bind('command+s', (e: any) => {
59 | e.preventDefault();
60 | const currentSchema = ctx.project.currentDocument?.exportSchema();
61 | options.cb(currentSchema);
62 | });
63 | }
64 | };
65 | };
66 | saveHotKey.pluginName = 'saveHotKey';
67 | saveHotKey.meta = {
68 | preferenceDeclaration: {
69 | title: '保存快捷键回调',
70 | properties: [{
71 | key: 'cb',
72 | type: 'Function',
73 | description: '用户自定义保存快捷键回调',
74 | }]
75 | }
76 | };
77 |
78 | export const publish = (ctx: ILowCodePluginContext, options: any) => {
79 | return {
80 | name: 'publish',
81 | async init() {
82 | const { skeleton } = ctx;
83 |
84 | skeleton.add({
85 | name: 'publish',
86 | area: 'topArea',
87 | type: 'Widget',
88 | props: {
89 | align: 'right'
90 | },
91 | content: Save,
92 | contentProps: {
93 | ctx,
94 | cb: options.cb,
95 | type: 'publish'
96 | }
97 | });
98 | }
99 | };
100 | };
101 | publish.pluginName = 'publish';
102 | publish.meta = {
103 | preferenceDeclaration: {
104 | title: '发布回调',
105 | properties: [{
106 | key: 'cb',
107 | type: 'Function',
108 | description: '用户自定义发布处理',
109 | }]
110 | }
111 | };
--------------------------------------------------------------------------------
/packages/plugin-tools/src/tools/zoomIn.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ILowCodePluginContext, hotkey } from '@alilc/lowcode-engine';
3 | import { EditorCommand } from '../common';
4 | class Tool extends React.Component<{ ctx: ILowCodePluginContext }, any> {
5 | handleClick() {
6 | const { ctx } = this.props;
7 | ctx.event.emit(EditorCommand.ZoomIn);
8 | }
9 | render() {
10 | return (
11 |
12 | 放大
13 |
14 | )
15 | }
16 | }
17 |
18 | export const zoomIn = (ctx: ILowCodePluginContext) => {
19 | return {
20 | name: 'zoomIn',
21 | async init() {
22 | const { skeleton } = ctx;
23 |
24 | skeleton.add({
25 | name: 'zoomIn',
26 | area: 'toolbar',
27 | type: 'Widget',
28 | props: {
29 | align: 'left'
30 | },
31 | content: Tool,
32 | contentProps: {
33 | ctx
34 | }
35 | });
36 | },
37 | };
38 | };
39 | zoomIn.pluginName = 'zoomIn';
40 |
41 | export const zoomInHotKey = (ctx: ILowCodePluginContext, options: any) => {
42 | return {
43 | name: 'zoomInHotKey',
44 | async init() {
45 | hotkey.bind('command++', (e: any) => {
46 | e.preventDefault();
47 | ctx.event.emit(EditorCommand.ZoomIn);
48 | });
49 | }
50 | };
51 | };
52 | zoomInHotKey.pluginName = 'zoomInHotKey';
--------------------------------------------------------------------------------
/packages/plugin-tools/src/tools/zoomOut.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
3 | import { EditorCommand } from '../common';
4 |
5 |
6 | class Tool extends React.Component<{ ctx: ILowCodePluginContext }, any> {
7 | handleClick() {
8 | const { ctx } = this.props;
9 | ctx.event.emit(EditorCommand.ZoomOut);
10 | }
11 | render() {
12 | return (
13 |
14 | 缩小
15 |
16 | )
17 | }
18 | }
19 |
20 | export const zoomOut = (ctx: ILowCodePluginContext) => {
21 | return {
22 | name: 'zoomOut',
23 | async init() {
24 | const { skeleton } = ctx;
25 |
26 | skeleton.add({
27 | name: 'zoomOut',
28 | area: 'toolbar',
29 | type: 'Widget',
30 | props: {
31 | align: 'left'
32 | },
33 | content: Tool,
34 | contentProps: {
35 | ctx
36 | }
37 | });
38 | },
39 | };
40 | };
41 | zoomOut.pluginName = 'zoomOut';
--------------------------------------------------------------------------------
/packages/plugin-tools/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/README.md:
--------------------------------------------------------------------------------
1 | ## x6 画布插件
2 |
3 | 设计要求:
4 | - 节点/线 组件统一使用 react 组件声明式封装
5 | - 节点/线 核心事件分发到组件,提供统一 x6Designer api
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-x6-designer",
3 | "version": "1.0.12",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch",
15 | "prepublish": "npm run build"
16 | },
17 | "license": "MIT",
18 | "publishConfig": {
19 | "access": "public",
20 | "registry": "https://registry.npmjs.org/"
21 | },
22 | "dependencies": {
23 | "@alilc/lce-graph-tools": "1.0.12",
24 | "@alilc/lowcode-utils": "^1.0.0",
25 | "@antv/x6-react-shape": "^1.6.1"
26 | },
27 | "devDependencies": {
28 | "@alib/build-scripts": "^0.1.18",
29 | "@antv/x6": "^1.31.4",
30 | "build-plugin-component": "^1.10.0"
31 | },
32 | "gitHead": "7ed863fe077671622a9901f670a9311aa85c3615"
33 | }
34 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/DesignerView.tsx:
--------------------------------------------------------------------------------
1 | import { createElement, Fragment, PureComponent } from "react";
2 | import { ILowCodePluginContext } from '@alilc/lowcode-engine';
3 | import { Editor } from '@alilc/lowcode-editor-core';
4 | import { initGraph } from "./graph/initGraph";
5 | import Nodes from "./items";
6 | import { render } from "react-dom";
7 | import { registerShape } from "./graph/registerShape";
8 | import { initEvents } from "./graph/initEvents";
9 | import x6Designer from './designer';
10 |
11 | interface IProps {
12 | editor: Editor;
13 | ctx: ILowCodePluginContext;
14 | }
15 |
16 | export default class DesignerView extends PureComponent {
17 | private container: HTMLDivElement;
18 | private nodesContainer: HTMLDivElement;
19 |
20 | refContainer = (container: HTMLDivElement) => {
21 | this.container = container;
22 | }
23 |
24 | refNodesContainer = (container: HTMLDivElement) => {
25 | this.nodesContainer = container;
26 | }
27 |
28 | componentDidMount() {
29 | registerShape();
30 |
31 | // @ts-ignore
32 | const graph = initGraph(this.container, []);
33 | if (graph) {
34 | x6Designer.init(this.props.ctx, graph);
35 | initEvents(graph);
36 |
37 | // add nodes & edges
38 | render(
39 | createElement(Nodes, {
40 | graph,
41 | ctx: this.props.ctx,
42 | }),
43 | this.nodesContainer
44 | );
45 |
46 | }
47 | }
48 |
49 | render() {
50 | return (
51 |
54 | )
55 | }
56 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/builtin-commands/index.ts:
--------------------------------------------------------------------------------
1 | export * from './remove-item';
2 | export * from './zoom-in-out';
3 | export * from './undo-redo';
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/builtin-commands/remove-item.ts:
--------------------------------------------------------------------------------
1 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
2 | export const removeItem = (ctx: IPublicModelPluginContext, graph: any, selectedNodes: any[]) => {
3 | if (selectedNodes?.length) {
4 | selectedNodes.forEach(node => {
5 | // 可被删除判断
6 | if (node?.componentMeta?.getMetadata().tags?.includes('node')) {
7 | const allNodes = Array.from(ctx.project.currentDocument?.nodesMap.values() || []);
8 | // 相关线
9 | const lines = allNodes.filter(n => (n?.componentMeta?.getMetadata().tags?.includes('edge') && (n.getPropValue('source') === node.id || n.getPropValue('target') === node.id)));
10 | lines.forEach(line => {
11 | ctx.project.currentDocument?.removeNode(line.id);
12 | });
13 | }
14 | ctx.project.currentDocument?.removeNode(node.id);
15 | });
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/builtin-commands/undo-redo.ts:
--------------------------------------------------------------------------------
1 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
2 | export const undo = (ctx: IPublicModelPluginContext, graph: any) => {
3 | ctx.project.currentDocument?.history.back();
4 | }
5 | export const redo = (ctx: IPublicModelPluginContext, graph: any) => {
6 | ctx.project.currentDocument?.history.forward();
7 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/builtin-commands/zoom-in-out.ts:
--------------------------------------------------------------------------------
1 | import { IPublicModelPluginContext } from '@alilc/lowcode-types';
2 | export const zoomIn = (ctx: IPublicModelPluginContext, graph: any) => {
3 | graph.zoom(0.1);
4 | }
5 | export const zoomOut = (ctx: IPublicModelPluginContext, graph: any) => {
6 | graph.zoom(-0.1);
7 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/designer.ts:
--------------------------------------------------------------------------------
1 | import { Node as NodeModel } from '@alilc/lowcode-shell';
2 | import { Node, Edge, Graph } from '@antv/x6';
3 | import { EditorCommand, CommandManager, ICommandCb } from '@alilc/lce-graph-tools';
4 | import { zoomIn, zoomOut, removeItem, undo, redo } from './builtin-commands';
5 |
6 | export interface IDesigner {
7 | onNodeRender: (cb: (model: NodeModel, node: Node) => void) => any[];
8 | onEdgeRender: (cb: (model: NodeModel, edge: Edge) => void) => any[];
9 | onEdgeLabelRender: (cb: (args: Graph.Hook.OnEdgeLabelRenderedArgs) => void) => any[];
10 | getGraph: () => Graph;
11 | }
12 |
13 | /**
14 | * designer 统一导出 api
15 | */
16 | class Designer implements IDesigner {
17 | constructor() {
18 | this.commandManager = new CommandManager({
19 | [EditorCommand.ZoomIn]: zoomIn,
20 | [EditorCommand.ZoomOut]: zoomOut,
21 | [EditorCommand.removeItem]: removeItem,
22 | [EditorCommand.Undo]: undo,
23 | [EditorCommand.Redo]: redo
24 | });
25 | }
26 | commandManager: CommandManager;
27 | public onNodeRenderCb: Array<(model: NodeModel, node: Node) => void> = [];
28 | public onEdgeRenderCb: Array<(model: NodeModel, edge: Edge) => void> = [];
29 | public onEdgeLabelRenderCb: Array<(args: Graph.Hook.OnEdgeLabelRenderedArgs) => void> = [];
30 | public graph: Graph;
31 |
32 | // node model data => graph node render
33 | public onNodeRender = (cb?: (model: NodeModel, node: Node) => void) => {
34 | if (cb) {
35 | this.onNodeRenderCb.push(cb);
36 | }
37 | return this.onNodeRenderCb;
38 | }
39 |
40 | // edge model data => graph edge render
41 | onEdgeRender = (cb?: (model: NodeModel, edge: Edge) => void) => {
42 | if (cb) {
43 | this.onEdgeRenderCb.push(cb);
44 | }
45 | return this.onEdgeRenderCb;
46 | }
47 |
48 | onEdgeLabelRender = (cb?: (args: Graph.Hook.OnEdgeLabelRenderedArgs) => void) => {
49 | if (cb) {
50 | this.onEdgeLabelRenderCb.push(cb);
51 | }
52 | return this.onEdgeLabelRenderCb;
53 | }
54 |
55 | init(ctx: any, graph: Graph) {
56 | this.graph = graph;
57 | this.commandManager.init(ctx, graph);
58 | }
59 |
60 | getGraph() {
61 | return this.graph;
62 | }
63 |
64 | public registerCommand = (key: string, listener: ICommandCb) => {
65 | this.commandManager.register(key, listener);
66 | }
67 | }
68 |
69 | const x6Designer = new Designer();
70 | export default x6Designer;
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/initGraph.tsx:
--------------------------------------------------------------------------------
1 | import { Graph, Shape } from '@antv/x6';
2 | import { project } from '@alilc/lowcode-engine';
3 | import x6Designer from '../designer';
4 |
5 | export function initGraph(container: HTMLElement) {
6 | //@ts-ignore
7 | const graph = window._X6Graph = new Graph({
8 | container,
9 | // async: true, // 异步加载画布
10 | grid: {
11 | // 网格
12 | size: 10,
13 | visible: true,
14 | type: 'dot', // 'dot' | 'fixedDot' | 'mesh'
15 | args: {
16 | color: '#919BAE', // 网格线/点颜色
17 | thickness: 1, // 网格线宽度/网格点大小
18 | },
19 | },
20 | panning: {
21 | enabled: true,
22 | eventTypes: ['mouseWheel']
23 | },
24 | clipboard: false,
25 | snapline: true, // 对齐线
26 | // https://github.com/antvis/X6/pull/2342 多选移动会和 sanpline 计算冲突,x6 bug 暂时不支持多选移动
27 | selecting: {
28 | enabled: true,
29 | rubberband: true,
30 | modifiers: ['shift'],
31 | },
32 | connecting: {
33 | snap: {
34 | radius: 40, // 吸附阈值
35 | },
36 | allowBlank: false, // 不允许连接到画布空白位置的点
37 | allowLoop: false, // 不允许创建循环连线
38 | allowMulti: false, // 不允许在相同的起始节点和终止之间创建多条边
39 | allowNode: false,
40 | allowEdge: true,
41 | allowPort: true,
42 | highlight: true,
43 | createEdge() {
44 | // 创建新边
45 | return new Shape.Edge({
46 | attrs: {
47 | line: {
48 | strokeDasharray: '5 5',
49 | stroke: '#4C6079',
50 | strokeOpacity: 0.5,
51 | strokeWidth: 1,
52 | targetMarker: {
53 | // 箭头
54 | name: 'block',
55 | size: 8,
56 | },
57 | },
58 | },
59 | zIndex: 0,
60 | });
61 | },
62 | validateEdge({ edge }) {
63 | const doc = project.currentDocument!;
64 | const contentEdge = doc.getNodeById(edge.id);
65 | console.log(edge.getSourceCellId(), edge.getTargetCellId());
66 | if (!contentEdge) {
67 | const node = doc.createNode({
68 | componentName: 'Line',
69 | title: '线',
70 | props: {
71 | name: '线',
72 | source: edge.getSourceCellId(),
73 | target: edge.getTargetCellId(),
74 | sourcePortId: edge.getSourcePortId(),
75 | targetPortId: edge.getTargetPortId()
76 | },
77 | });
78 | const rootNode = project.currentDocument?.root;
79 | project.currentDocument?.insertNode(rootNode!, node);
80 | } else {
81 | contentEdge.setPropValue('source', edge.getSourceCellId());
82 | contentEdge.setPropValue('target', edge.getTargetCellId());
83 | contentEdge.setPropValue('sourcePortId', edge.getSourcePortId());
84 | contentEdge.setPropValue('targetPortId', edge.getTargetPortId());
85 | }
86 |
87 | return false;
88 | },
89 | },
90 | onEdgeLabelRendered(args) {
91 | const onEdgeLabelRenderCb = x6Designer.onEdgeLabelRender();
92 | for (const cb of onEdgeLabelRenderCb) {
93 | cb(args);
94 | }
95 | }
96 | });
97 |
98 | // 适应画布
99 | const getContainerSize = () => {
100 | const leftPanel = document.querySelector('.lc-left-area')?.clientWidth || 0;
101 | const rightPanel =
102 | document.querySelector('.lc-right-area')?.clientWidth || 0;
103 | return {
104 | width: document.body.offsetWidth - leftPanel - rightPanel,
105 | height: document.querySelector('.lc-main-area')?.clientHeight || 0,
106 | };
107 | };
108 | const resizeFn = () => {
109 | const { width, height } = getContainerSize();
110 | graph.resize(width, height);
111 | };
112 | window.addEventListener('resize', resizeFn);
113 |
114 | // 画布内容居中
115 | requestAnimationFrame(() => {
116 | resizeFn();
117 | graph.centerContent();
118 | });
119 | return graph;
120 | }
121 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/register/shape/const.ts:
--------------------------------------------------------------------------------
1 | export const DeepBlueColor = '#4C6079';
2 | export const BlueColor = '#027AFF';
3 | export const portR = 5;
4 | export const strokeWidth = 1;
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/register/shape/lce-circle.ts:
--------------------------------------------------------------------------------
1 | export const LceCircle = {
2 | inherit: 'circle',
3 | width: 50,
4 | height: 50,
5 | attrs: {
6 | circle: {
7 | stroke: '#4C6079',
8 | strokeWidth: 1,
9 | fill: '#fff',
10 | r: 50,
11 | },
12 | text: {
13 | fontSize: 12,
14 | fill: '#4C6079',
15 | x: 10,
16 | },
17 | },
18 | ports: {
19 | groups: {
20 | top: {
21 | position: 'top',
22 | attrs: {
23 | circle: {
24 | r: 3,
25 | magnet: true,
26 | stroke: '#A5AFBC',
27 | strokeWidth: 1,
28 | fill: '#fff',
29 | style: {
30 | visibility: 'hidden',
31 | },
32 | },
33 | },
34 | zIndex: 99,
35 | },
36 | right: {
37 | position: 'right',
38 | attrs: {
39 | circle: {
40 | r: 3,
41 | magnet: true,
42 | stroke: '#A5AFBC',
43 | strokeWidth: 1,
44 | fill: '#fff',
45 | style: {
46 | visibility: 'hidden',
47 | },
48 | },
49 | },
50 | zIndex: 99,
51 | },
52 | bottom: {
53 | position: 'bottom',
54 | attrs: {
55 | circle: {
56 | r: 3,
57 | magnet: true,
58 | stroke: '#A5AFBC',
59 | strokeWidth: 1,
60 | fill: '#fff',
61 | style: {
62 | visibility: 'hidden',
63 | },
64 | },
65 | },
66 | zIndex: 99,
67 | },
68 | left: {
69 | position: 'left',
70 | attrs: {
71 | circle: {
72 | r: 3,
73 | magnet: true,
74 | stroke: '#A5AFBC',
75 | strokeWidth: 1,
76 | fill: '#fff',
77 | style: {
78 | visibility: 'hidden',
79 | },
80 | },
81 | },
82 | zIndex: 99,
83 | },
84 | },
85 | items: [
86 | {
87 | id: 't',
88 | group: 'top',
89 | },
90 | {
91 | id: 'r',
92 | group: 'right',
93 | },
94 | {
95 | id: 'b',
96 | group: 'bottom',
97 | },
98 | {
99 | id: 'l',
100 | group: 'left',
101 | },
102 | ],
103 | },
104 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/register/shape/lce-diamond.ts:
--------------------------------------------------------------------------------
1 | import { BlueColor, DeepBlueColor, portR, strokeWidth } from "./const";
2 |
3 | export const LceDiamond = {
4 | inherit: 'polygon',
5 | width: 124,
6 | height: 70,
7 | attrs: {
8 | body: {
9 | stroke: DeepBlueColor,
10 | strokeWidth,
11 | points: "0, 35, 62, 0, 124, 35, 62, 70",
12 | fill: '#fff',
13 | rx: 10,
14 | ry: 10
15 | },
16 | text: {
17 | width: 60,
18 | height: 32,
19 | fontSize: 12,
20 | fill: DeepBlueColor,
21 | x: 13,
22 | lineHeight: 16,
23 | textWrap: {
24 | width: 50,
25 | height: 14,
26 | ellipsis: true, // 文本超出显示范围时,自动添加省略号
27 | breakWord: true, // 是否截断单词
28 | }
29 | },
30 | image: {
31 | width: 20,
32 | height: 20,
33 | x: 30,
34 | y: 24,
35 | },
36 | },
37 | markup: [
38 | {
39 | tagName: 'polygon',
40 | selector: 'body',
41 | },
42 | {
43 | tagName: 'text',
44 | selector: 'text',
45 | },
46 | {
47 | tagName: 'image',
48 | selector: 'image',
49 | },
50 | ],
51 | ports: {
52 | groups: {
53 | top: {
54 | position: 'top',
55 | attrs: {
56 | circle: {
57 | r: portR,
58 | magnet: true,
59 | stroke: BlueColor,
60 | strokeWidth: 1,
61 | fill: '#fff',
62 | style: {
63 | visibility: 'hidden',
64 | opacity: '1'
65 | },
66 | },
67 | },
68 | zIndex: 99,
69 | },
70 | right: {
71 | position: 'right',
72 | attrs: {
73 | circle: {
74 | r: portR,
75 | magnet: true,
76 | stroke: BlueColor,
77 | strokeWidth: 1,
78 | fill: '#fff',
79 | style: {
80 | visibility: 'hidden',
81 | opacity: '1'
82 | },
83 | },
84 | },
85 | zIndex: 99,
86 | },
87 | bottom: {
88 | position: 'bottom',
89 | attrs: {
90 | circle: {
91 | r: portR,
92 | magnet: true,
93 | stroke: BlueColor,
94 | strokeWidth: 1,
95 | fill: '#fff',
96 | style: {
97 | visibility: 'hidden',
98 | opacity: '1'
99 | },
100 | },
101 | },
102 | zIndex: 99,
103 | },
104 | left: {
105 | position: 'left',
106 | attrs: {
107 | circle: {
108 | r: portR,
109 | magnet: true,
110 | stroke: BlueColor,
111 | strokeWidth: 1,
112 | fill: '#fff',
113 | style: {
114 | visibility: 'hidden',
115 | opacity: '1'
116 | },
117 | },
118 | },
119 | zIndex: 99,
120 | },
121 | },
122 | items: [
123 | {
124 | id: 't',
125 | group: 'top',
126 | },
127 | {
128 | id: 'r',
129 | group: 'right',
130 | },
131 | {
132 | id: 'b',
133 | group: 'bottom',
134 | },
135 | {
136 | id: 'l',
137 | group: 'left',
138 | },
139 | ],
140 | },
141 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/register/shape/lce-rect.ts:
--------------------------------------------------------------------------------
1 | import { portR, BlueColor, strokeWidth } from "./const";
2 |
3 | export const LceRect = {
4 | inherit: 'rect',
5 | width: 124,
6 | height: 42,
7 | attrs: {
8 | body: {
9 | stroke: '#4C6079',
10 | strokeWidth,
11 | fill: '#fff',
12 | rx: 21,
13 | ry: 21,
14 | },
15 | text: {
16 | width: 60,
17 | height: 32,
18 | fontSize: 12,
19 | fill: '#4C6079',
20 | x: 10,
21 | lineHeight: 16,
22 | textWrap: {
23 | width: 60,
24 | height: '80%',
25 | ellipsis: true, // 文本超出显示范围时,自动添加省略号
26 | breakWord: true, // 是否截断单词
27 | }
28 | },
29 | image: {
30 | width: 30,
31 | height: 30,
32 | x: 8,
33 | y: 6,
34 | },
35 | },
36 | markup: [
37 | {
38 | tagName: 'rect',
39 | selector: 'body',
40 | },
41 | {
42 | tagName: 'text',
43 | selector: 'text',
44 | },
45 | {
46 | tagName: 'image',
47 | selector: 'image',
48 | },
49 | ],
50 | ports: {
51 | groups: {
52 | top: {
53 | position: 'top',
54 | attrs: {
55 | circle: {
56 | r: portR,
57 | magnet: true,
58 | stroke: BlueColor,
59 | strokeWidth: 1,
60 | fill: '#fff',
61 | style: {
62 | visibility: 'hidden',
63 | opacity: '1'
64 | },
65 | },
66 | },
67 | zIndex: 99,
68 | },
69 | right: {
70 | position: 'right',
71 | attrs: {
72 | circle: {
73 | r: portR,
74 | magnet: true,
75 | stroke: BlueColor,
76 | strokeWidth: 1,
77 | fill: '#fff',
78 | style: {
79 | visibility: 'hidden',
80 | opacity: '1'
81 | },
82 | },
83 | },
84 | zIndex: 99,
85 | },
86 | bottom: {
87 | position: 'bottom',
88 | attrs: {
89 | circle: {
90 | r: portR,
91 | magnet: true,
92 | stroke: BlueColor,
93 | strokeWidth: 1,
94 | fill: '#fff',
95 | style: {
96 | visibility: 'hidden',
97 | opacity: '1'
98 | },
99 | },
100 | },
101 | zIndex: 99,
102 | },
103 | left: {
104 | position: 'left',
105 | attrs: {
106 | circle: {
107 | r: portR,
108 | magnet: true,
109 | stroke: BlueColor,
110 | strokeWidth: 1,
111 | fill: '#fff',
112 | style: {
113 | visibility: 'hidden',
114 | opacity: '1'
115 | },
116 | },
117 | },
118 | zIndex: 99,
119 | },
120 | },
121 | items: [
122 | {
123 | id: 't',
124 | group: 'top',
125 | },
126 | {
127 | id: 'r',
128 | group: 'right',
129 | },
130 | {
131 | id: 'b',
132 | group: 'bottom',
133 | },
134 | {
135 | id: 'l',
136 | group: 'left',
137 | },
138 | ],
139 | },
140 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/registerShape.ts:
--------------------------------------------------------------------------------
1 | import { LceCircle } from './register/shape/lce-circle';
2 | import { Graph } from '@antv/x6';
3 | import { LceDiamond } from './register/shape/lce-diamond';
4 | import { LceRect } from './register/shape/lce-rect';
5 |
6 | function registerNode(nodeName: string, node: any) {
7 | // 在 registerNode 前进行 unregisterNode, 防止在多资源场景下由于注册重复的 node 导致报错。
8 | Graph.unregisterNode(nodeName);
9 | Graph.registerNode(nodeName, node);
10 | }
11 |
12 | /**
13 | * 注册 shape
14 | */
15 | export function registerShape() {
16 | registerNode('lce-rect', LceRect);
17 |
18 | registerNode('lce-diamond', LceDiamond);
19 |
20 | registerNode('lce-circle', LceCircle);
21 | }
22 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/graph/util.ts:
--------------------------------------------------------------------------------
1 | import { FunctionExt, EdgeView, CellView, Edge, Point, Registry, Line } from '@antv/x6';
2 |
3 | /**
4 | * 合并 points
5 | * 三个点在一条直线上的,删掉中间点
6 | * @param edgeView
7 | * @param routerPoints
8 | * @returns
9 | */
10 | export function getVertices(edgeView: EdgeView, routerPoints: Array) {
11 | const source = edgeView.sourceAnchor;
12 | const target = edgeView.targetAnchor;
13 |
14 | const points = [source, ...routerPoints, target]
15 | let indexArr: any[] = [];
16 | if (points.length > 2) {
17 | for (let i = 0; i <= points.length - 3; i++) {
18 | const isXEqual = Math.abs(points[i].x - points[i+1].x) < 1 && Math.abs(points[i+1].x - points[i+2].x) < 1;
19 | const isYEqual = Math.abs(points[i].y - points[i+1].y) < 1 && Math.abs(points[i+1].y - points[i+2].y) < 1;
20 | if (isXEqual || isYEqual) {
21 | indexArr.push(i+1);
22 | }
23 | }
24 | }
25 |
26 | const newPoints = points.filter((v, index) => !(indexArr as any).includes(index));
27 | return newPoints;
28 | }
29 |
30 | /**
31 | * 节点 ports 显示隐藏
32 | * @param ports
33 | * @param show
34 | */
35 | export function showPorts(ports: NodeListOf, show: boolean) {
36 | for (let i = 0, len = ports.length; i < len; i = i + 1) {
37 | ports[i].style.visibility = show ? 'visible' : 'hidden';
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { ILowCodePluginContext, project } from '@alilc/lowcode-engine';
2 | import DesignerView from './DesignerView';
3 | import { rootState } from './items/state';
4 | import x6Designer, { IDesigner } from './designer';
5 | import '@antv/x6-react-shape'; // 支持自定义 react 组件
6 |
7 | /**
8 | * plugin X6 designer
9 | * @param ctx
10 | * @returns
11 | */
12 | const PluginX6Designer = (ctx: ILowCodePluginContext) => {
13 | return {
14 | exports() {
15 | return x6Designer;
16 | },
17 | init() {
18 | const { skeleton, project } = ctx;
19 | skeleton.remove({
20 | name: 'designer',
21 | area: 'mainArea',
22 | type: 'Widget'
23 | });
24 | skeleton.add({
25 | area: 'mainArea',
26 | name: 'designer',
27 | type: 'Widget',
28 | content: DesignerView,
29 | contentProps: {
30 | ctx,
31 | }
32 | });
33 |
34 | // bind nodes state
35 | rootState.bindNodes(project.currentDocument);
36 |
37 | project.onChangeDocument((doc) => {
38 | rootState.disposeDocumentEvent();
39 | rootState.bindNodes(project.currentDocument);
40 | });
41 | }
42 | }
43 | }
44 |
45 | PluginX6Designer.pluginName = 'plugin-x6-designer';
46 | export default PluginX6Designer;
47 | export { IDesigner };
48 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/edge/index.tsx:
--------------------------------------------------------------------------------
1 | import { Graph, Edge } from '@antv/x6';
2 | import React from 'react';
3 | import { Node as NodeModel } from '@alilc/lowcode-shell';
4 | import { getComponentView, updateNodeProps } from '../utils';
5 | import designer from '../../designer';
6 |
7 | interface Props {
8 | onMountEdge: (edge: Edge) => void;
9 | onUnMountEdge: (edge: Edge) => void;
10 |
11 | graph: Graph;
12 | model: NodeModel;
13 | ctx: any;
14 | }
15 |
16 | /**
17 | * edge component for x6 edge render
18 | */
19 | class EdgeComponent extends React.PureComponent {
20 | private edge: Edge;
21 |
22 | componentDidMount() {
23 | const { model, graph, ctx } = this.props;
24 | const { project } = ctx;
25 |
26 | // 创建 edge
27 | const view = getComponentView(model);
28 | this.edge = graph.createEdge({
29 | id: model.id,
30 | ...view
31 | });
32 |
33 | // 收集 edge 统一添加到画布
34 | this.props.onMountEdge(this.edge);
35 |
36 | // set edge vertices
37 | // @ts-ignore
38 | const { source, target } = model.propsData;
39 |
40 | // set source & target
41 | this.edge.setSource({ cell: source });
42 | this.edge.setTarget({ cell: target });
43 |
44 | // 渲染逻辑切面
45 | const onEdgeRender = designer.onEdgeRender();
46 | // 渲染逻辑切面
47 | for (const cb of onEdgeRender) {
48 | cb(model, this.edge);
49 | }
50 |
51 | // model 更新渲染
52 | project.currentDocument?.onChangeNodeProp(({ key, oldValue, newValue, node }) => {
53 | if (node.id !== model.id) {
54 | return;
55 | }
56 |
57 | if (key === 'source') {
58 | this.edge.setSource({ cell: newValue });
59 | }
60 |
61 | if (key === 'target') {
62 | this.edge.setTarget({ cell: newValue });
63 | }
64 |
65 | // 用户自定义渲染逻辑切面
66 | for (const cb of onEdgeRender) {
67 | cb(model, this.edge);
68 | }
69 | });
70 | }
71 |
72 | componentWillUnmount() {
73 | // 删除节点
74 | this.props.onUnMountEdge(this.edge);
75 | }
76 |
77 | render() {
78 | return null;
79 | }
80 | }
81 |
82 | export default EdgeComponent;
83 |
84 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/index.less:
--------------------------------------------------------------------------------
1 | .x6-widget-selection-box {
2 | z-index: -10;
3 | }
4 |
5 | .x6-widget-stencil-content {
6 | border: 1px solid #EDEDED;
7 | box-shadow: 0 4px 15px 0 rgb(31 56 88 / 15%);
8 | border-radius: 4px;
9 | background-color: #FFFFFF;
10 | }
11 |
12 | .next-overlay-wrapper .next-balloon {
13 | padding: 10px 16px;
14 | }
15 |
16 | .x6-port-body {
17 | &:hover {
18 | color: #4C6079;
19 | transform: scale(1.3);
20 | }
21 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/index.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { observer } from 'mobx-react';
3 | import NodeComponent from './node';
4 | import EdgeComponent from './edge';
5 | import { Node, Edge, Graph } from '@antv/x6';
6 | import { EdgeComponentName, rootState, RootState } from "./state";
7 | import './index.less';
8 |
9 | interface Props {
10 | graph: Graph;
11 | }
12 |
13 | /**
14 | * render node & edge model
15 | * observable nodes
16 | */
17 | @observer
18 | class Nodes extends React.PureComponent {
19 | nodes: Node[] = [];
20 | edges: Edge[] = [];
21 | mounted: boolean = false; // 是否 didMounted
22 |
23 | componentDidMount() {
24 | const { graph } = this.props;
25 | graph.resetCells([...this.nodes, ...this.edges]);
26 | this.mounted = true;
27 | }
28 |
29 | onMountEdge = (edge: Edge) => {
30 | const { graph } = this.props;
31 | this.edges.push(edge);
32 |
33 | if (this.mounted) {
34 | graph.addEdge(edge);
35 | }
36 | }
37 |
38 | onMountNode = (node: Node) => {
39 | const { graph } = this.props;
40 | this.nodes.push(node);
41 |
42 | if (this.mounted) {
43 | graph.addNode(node);
44 | }
45 | }
46 |
47 | onUnMountNode = (node: Node) => {
48 | const { graph } = this.props;
49 | const index = this.nodes.indexOf(node);
50 | this.nodes.splice(index, 1);
51 |
52 | if (this.mounted) {
53 | graph.removeCell(node);
54 | }
55 | }
56 |
57 | onUnMountEdge = (edge: Edge) => {
58 | const { graph } = this.props;
59 | const index = this.edges.indexOf(edge);
60 | this.edges.splice(index, 1);
61 |
62 | if (this.mounted) {
63 | graph.removeCell(edge);
64 | }
65 | }
66 |
67 | render() {
68 | const { graph } = this.props;
69 | const nodes = rootState.getNodes();
70 | const items = nodes.filter(v => typeof v.isPage === 'function' ? !v.isPage() : !v.isPage);
71 | return (
72 |
73 | {
74 | items.map(node => {
75 | if (node.componentMeta?.getMetadata().tags?.includes('edge')) {
76 | return (
77 |
85 | )
86 | } else {
87 | return (
88 | )
96 | }
97 | })
98 | }
99 |
100 | )
101 | }
102 | }
103 |
104 | export default Nodes;
105 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/node/index.tsx:
--------------------------------------------------------------------------------
1 | import { Graph, Node } from '@antv/x6';
2 | import React from 'react';
3 | import { Node as NodeModel } from '@alilc/lowcode-shell';
4 | import designer from '../../designer';
5 | import { getComponentView, updateNodeProps } from '../utils';
6 |
7 | interface Props {
8 | onMountNode: (node: Node) => void;
9 | onUnMountNode: (node: Node) => void;
10 |
11 | graph: typeof Graph;
12 | model: NodeModel;
13 | ctx: any;
14 | }
15 |
16 |
17 |
18 | /**
19 | * node component for x6 node render
20 | */
21 | class NodeComponent extends React.PureComponent {
22 | private node: Node;
23 | // 节点区分是否有自定义html渲染
24 | private nodeDefinedType: 'shape' | 'component';
25 |
26 | componentDidMount() {
27 | // 添加节点
28 | const { model, graph, ctx } = this.props;
29 | const { project } = ctx;
30 | const view = getComponentView(model);
31 | this.nodeDefinedType = view?.component ? 'component' : 'shape';
32 | // 基于 Schema 数据恢复节点,保持 id 和 ports 一致
33 | this.node = graph.createNode({
34 | id: model.id,
35 | ports: model.propsData.ports,
36 | ...view
37 | });
38 |
39 | // 收集 node 统一添加到画布
40 | this.props.onMountNode(this.node);
41 |
42 | // @ts-ignore
43 | const { position } = model.propsData;
44 | // 定位
45 | this.node.setPosition(position);
46 | // 加载自定义节点渲染逻辑
47 | const onNodeRenderCb = designer.onNodeRender();
48 |
49 | // 用户自定义渲染逻辑切面
50 | if (this.nodeDefinedType === 'shape' && onNodeRenderCb.length > 0) {
51 | for (const cb of onNodeRenderCb) {
52 | cb(model, this.node);
53 | }
54 | } else {
55 | updateNodeProps(model, this.node);
56 | }
57 |
58 | // model 更新触发渲染
59 | project.currentDocument?.onChangeNodeProp(({ key, oldValue, newValue, node }) => {
60 | if (node.id !== model.id) {
61 | return;
62 | }
63 |
64 | if (key === 'position') {
65 | this.node.setPosition(newValue);
66 | return;
67 | }
68 |
69 | // 用户自定义渲染逻辑切面
70 | if (this.nodeDefinedType === 'shape' && onNodeRenderCb.length > 0) {
71 | for (const cb of onNodeRenderCb) {
72 | cb(model, this.node);
73 | }
74 | } else {
75 | this.node.prop(key, newValue);
76 | }
77 | });
78 | }
79 |
80 | componentWillUnmount() {
81 | // 删除节点
82 | this.props.onUnMountNode(this.node);
83 | }
84 |
85 | render() {
86 | return null;
87 | }
88 | }
89 |
90 | export default NodeComponent;
91 |
92 |
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/state.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, makeObservable } from "mobx";
2 | import { IPublicModelDocumentModel } from '@alilc/lowcode-types';
3 |
4 | export const EdgeComponentName = 'Line'; // 边 组件独一 componentName
5 |
6 |
7 | export class RootState {
8 | @observable.shallow private nodes: any[] = []; // 单独存一份 nodes 状态数据
9 |
10 | documentEvent: Function[] = [];
11 |
12 | constructor() {
13 | makeObservable(this);
14 | }
15 |
16 | @action
17 | setNodes(nodes: any[]) {
18 | this.nodes = nodes;
19 | }
20 |
21 | getNodes() {
22 | return this.nodes;
23 | }
24 |
25 | disposeDocumentEvent() {
26 | this.documentEvent.forEach(d => d && d());
27 | this.documentEvent = [];
28 | }
29 |
30 | bindNodes(document: IPublicModelDocumentModel | null) {
31 | if (document) {
32 | this.nodes = Array.from(document.nodesMap.values() || []);
33 | this.documentEvent = [
34 | document.onImportSchema(() => {
35 | this.setNodes(Array.from(document.nodesMap.values() || []));
36 | }),
37 | document.onAddNode((node) => {
38 | this.nodes.push(node);
39 | this.setNodes(this.nodes);
40 | }),
41 | document.onRemoveNode((node) => {
42 | const _nodes = this.nodes.filter(v => v.id !== node.id);
43 | this.setNodes(_nodes);
44 | }),
45 | // 撤销恢复不会触发 onAddNode 和 onRemoveNode 事件
46 | document.history.onChangeState((...data: any) => {
47 | this.nodes = Array.from(document.nodesMap.values() || []);
48 | }),
49 | ];
50 | }
51 | }
52 | }
53 |
54 | export const rootState = new RootState();
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/src/items/utils.ts:
--------------------------------------------------------------------------------
1 | import { Node } from '@antv/x6';
2 | import { Node as NodeModel } from '@alilc/lowcode-shell';
3 | import { material } from '@alilc/lowcode-engine';
4 | import { get } from 'lodash';
5 |
6 | export const getComponentView = (nodeModel: NodeModel) => {
7 | const { componentName } = nodeModel;
8 | const global: any = window;
9 | const assets = material.getAssets();
10 | const componentMeta = material.getComponentMeta(componentName);
11 |
12 | if (!componentMeta || !componentMeta.npm) {
13 | console.error(`${componentName} is not a npm component`);
14 | return null;
15 | }
16 |
17 | const { package: componentPackage, destructuring, exportName } = componentMeta.npm;
18 |
19 | const library = assets.packages.find((item: any) => item.package === componentPackage).library;
20 |
21 | if (!library) {
22 | console.error(`${componentName} library is not defined`);
23 | return null;
24 | }
25 |
26 | if (destructuring && exportName) {
27 | return typeof global[library][exportName] === 'function' ? global[library][exportName]() : global[library][exportName];
28 | }
29 |
30 | return typeof global[library] === 'function' ? global[library]() : global[library];
31 | }
32 |
33 | export function getPropList(model: NodeModel) {
34 | const configure = model.componentMeta?.configure;
35 |
36 | if (!Array.isArray(configure)) {
37 | return [];
38 | }
39 |
40 | const props = configure.find(item => item.name === '#props');
41 |
42 | if (!Array.isArray(props?.items)) {
43 | return [];
44 | }
45 |
46 | const propsData = model.propsData || {};
47 |
48 | return props?.items?.map(item => {
49 | const value = get(propsData, item.name) ?? item.defaultValue;
50 | return {
51 | name: item.name,
52 | value,
53 | };
54 | });
55 | }
56 |
57 | export function updateNodeProps(model: NodeModel, node: Node) {
58 | const propList = getPropList(model) || [];
59 | propList.forEach(item => {
60 | node.prop(item.name, item.value);
61 | });
62 | }
--------------------------------------------------------------------------------
/packages/plugin-x6-designer/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/scripts/create.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | DIR_NAME=$1
4 |
5 | if [ -z $DIR_NAME ];then
6 | echo 'Usage: ./create.sh '
7 | exit 0
8 | fi
9 |
10 | if [ -d packages/$DIR_NAME ];then
11 | echo 'Folder is existing!'
12 | exit 0
13 | fi
14 |
15 | mkdir packages/$DIR_NAME
16 |
17 | cp -r templates/* packages/$DIR_NAME
18 |
19 | mv packages/$DIR_NAME/_tsconfig.json packages/$DIR_NAME/tsconfig.json
20 |
21 | echo 'Add package successfully.'
22 |
--------------------------------------------------------------------------------
/scripts/publish.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | WORK_DIR=$PWD
4 | BUILD_DEST=$1
5 |
6 | if [ ! -d "$BUILD_DEST" ]; then
7 | mkdir -p "$BUILD_DEST"
8 | fi
9 |
10 | npm i -g n
11 | # 使用官方源有较大概率会 block
12 | export N_NODE_MIRROR=https://npm.taobao.org/mirrors/node
13 |
14 | echo "Switch node version to 16"
15 | n 16.15.1
16 | echo "Node Version"
17 | node -v
18 |
19 | echo "Deploy ${WORK_DIR} -> ${BUILD_DEST} ..."
20 |
21 | echo "Setup"
22 | npm run setup
23 | npm run build
24 |
25 | mv ./packages/bootstrap/build/* $BUILD_DEST
26 |
27 | echo "Complete"
28 |
--------------------------------------------------------------------------------
/scripts/setup.js:
--------------------------------------------------------------------------------
1 | const os = require('os');
2 | const del = require('del');
3 | const gulp = require('gulp');
4 | const execa = require('execa');
5 |
6 | async function deleteRootDirLockFile() {
7 | await del('package-lock.json');
8 | await del('yarn.lock');
9 | }
10 |
11 | async function clean() {
12 | await execa.command('lerna clean -y', { stdio: 'inherit', encoding: 'utf-8' });
13 | }
14 |
15 | async function deletePackagesDirLockFile() {
16 | await del('packages/**/package-lock.json');
17 | }
18 |
19 | async function bootstrap() {
20 | await execa.command('lerna bootstrap --force-local', { stdio: 'inherit', encoding: 'utf-8' });
21 | }
22 |
23 | const setup = gulp.series(deleteRootDirLockFile, clean, deletePackagesDirLockFile, bootstrap);
24 |
25 | os.type() === 'Windows_NT' ? setup() : execa.command('scripts/setup.sh', { stdio: 'inherit', encoding: 'utf-8' });
--------------------------------------------------------------------------------
/scripts/setup.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | rm -rf package-lock.json yarn.lock
4 | lerna clean -y
5 | find ./packages -type f -name "package-lock.json" -exec rm -f {} \;
6 |
7 | lerna bootstrap --force-local
8 |
--------------------------------------------------------------------------------
/scripts/start.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | pkgName="@alilc/lce-graph"
4 |
5 | if [ "$1" ]; then
6 | pkgName="$1"
7 | fi
8 |
9 | lerna exec --scope $pkgName -- npm start
10 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const app = require('express')();
2 | const http = require('http').Server(app);
3 | const io = require('socket.io')(http, {
4 | cors: {
5 | origin: "http://localhost:6006",
6 | methods: ["GET", "POST"]
7 | }
8 | });
9 | const port = process.env.PORT || 3000;
10 |
11 | io.on('connection', (socket) => {
12 | socket.on('updateTree', msg => {
13 | console.log(msg);
14 | io.emit('updateTree', msg);
15 | });
16 | });
17 |
18 | http.listen(port, () => {
19 | console.log(`Socket.IO server running at http://localhost:${port}/`);
20 | });
21 |
--------------------------------------------------------------------------------
/templates/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "import",
5 | {
6 | "libraryName": "@alife/next",
7 | "libraryDirectory": "lib",
8 | "style": true,
9 | "camel2DashComponentName": true
10 | }
11 | ]
12 | ]
13 | }
--------------------------------------------------------------------------------
/templates/README.md:
--------------------------------------------------------------------------------
1 | TODO
2 | ---
3 |
--------------------------------------------------------------------------------
/templates/_tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "lib"
5 | },
6 | "include": [
7 | "./src/"
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/templates/build.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | [
4 | "build-plugin-component"
5 | ]
6 | ]
7 | }
8 |
--------------------------------------------------------------------------------
/templates/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@alilc/lce-graph-",
3 | "version": "1.0.0",
4 | "description": "for Graph Editor Engine",
5 | "main": "lib/index.js",
6 | "module": "es/index.js",
7 | "files": [
8 | "lib",
9 | "es"
10 | ],
11 | "scripts": {
12 | "test": "jest -c jest.config.js",
13 | "build": "build-scripts build --skip-demo",
14 | "build:watch": "build-scripts build --skip-demo --watch"
15 | },
16 | "license": "MIT",
17 | "publishConfig": {
18 | "access": "public",
19 | "registry": "https://registry.npmjs.org/"
20 | },
21 | "devDependencies": {
22 | "build-plugin-component": "^1.10.0",
23 | "@alib/build-scripts": "^0.1.18"
24 | }
25 | }
--------------------------------------------------------------------------------
/templates/src/index.ts:
--------------------------------------------------------------------------------
1 | //
2 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "lib": [
5 | "es2015",
6 | "dom"
7 | ],
8 | // Target latest version of ECMAScript.
9 | "target": "es6",
10 | // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
11 | "module": "esnext",
12 | // Search under node_modules for non-relative imports.
13 | "moduleResolution": "node",
14 | // Enable strictest settings like strictNullChecks & noImplicitAny.
15 | "strict": true,
16 | "strictPropertyInitialization": false,
17 | // Allow default imports from modules with no default export. This does not affect code emit, just typechecking.
18 | "allowSyntheticDefaultImports": true,
19 | // Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'.
20 | "esModuleInterop": true,
21 | // Specify JSX code generation: 'preserve', 'react-native', or 'react'.
22 | "jsx": "preserve",
23 | // Import emit helpers (e.g. __extends, __rest, etc..) from tslib
24 | "importHelpers": true,
25 | // Enables experimental support for ES7 decorators.
26 | "experimentalDecorators": true,
27 | "emitDecoratorMetadata": true,
28 | // Generates corresponding .map file.
29 | "sourceMap": true,
30 | // Disallow inconsistently-cased references to the same file.
31 | "forceConsistentCasingInFileNames": true,
32 | // Allow json import
33 | "resolveJsonModule": true,
34 | // skip type checking of declaration files
35 | "skipLibCheck": true,
36 | "baseUrl": "./packages",
37 | "paths": {
38 | "@alilc/lce-graph-core": [
39 | "plugin-core/src"
40 | ],
41 | "@alilc/lce-graph-g6-designer": [
42 | "plugin-g6-designer/src"
43 | ],
44 | "@alilc/lce-graph-x6-designer": [
45 | "plugin-x6-designer/src"
46 | ],
47 | "@alilc/lce-graph-tools": [
48 | "plugin-tools/src"
49 | ],
50 | "@alilc/g-react-renderer": [
51 | "g-react-renderer/src"
52 | ],
53 | "@alilc/lce-graph-materials-pane": [
54 | "plugin-materials-pane/src"
55 | ],
56 | },
57 | "outDir": "lib"
58 | },
59 | "exclude": [
60 | "**/test",
61 | "**/lib",
62 | "**/es",
63 | "node_modules"
64 | ]
65 | }
--------------------------------------------------------------------------------