Browse Source

[Feature][UI Next] Auto layout modal (#8298)

3.0.0/version-upgrade
wangyizhi 3 years ago committed by GitHub
parent
commit
78cac08532
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      dolphinscheduler-ui-next/package.json
  2. 202
      dolphinscheduler-ui-next/pnpm-lock.yaml
  3. 5
      dolphinscheduler-ui-next/src/components/modal/index.tsx
  4. 10
      dolphinscheduler-ui-next/src/locales/modules/en_US.ts
  5. 10
      dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts
  6. 105
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-auto-layout-modal.tsx
  7. 2
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx
  8. 35
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx
  9. 4
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts
  10. 5
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx
  11. 46
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx
  12. 100
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss
  13. 41
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx
  14. 19
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts
  15. 177
      dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-auto-layout.ts
  16. 18
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.module.scss
  17. 15
      dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx

3
dolphinscheduler-ui-next/package.json

@ -10,6 +10,7 @@
"prettier": "prettier --write \"src/**/*.{vue,ts,tsx}\""
},
"dependencies": {
"@antv/layout": "^0.1.31",
"@antv/x6": "^1.29.5",
"@vueuse/core": "^7.5.3",
"axios": "^0.24.0",
@ -36,7 +37,7 @@
"@vicons/antd": "^0.11.0",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-vue-jsx": "^1.3.3",
"dart-sass": "^1.48.0",
"dart-sass": "^1.25.0",
"eslint": "^8.6.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",

202
dolphinscheduler-ui-next/pnpm-lock.yaml

@ -18,6 +18,7 @@
lockfileVersion: 5.3
specifiers:
'@antv/layout': ^0.1.31
'@antv/x6': ^1.29.5
'@types/node': ^16.11.19
'@types/nprogress': ^0.2.0
@ -57,6 +58,7 @@ specifiers:
vue-tsc: ^0.28.10
dependencies:
'@antv/layout': 0.1.31
'@antv/x6': 1.29.5
'@vueuse/core': 7.5.3_vue@3.2.26
axios: 0.24.0
@ -99,6 +101,58 @@ devDependencies:
packages:
/@antv/g-webgpu-core/0.5.6:
resolution: {integrity: sha512-DPiH3GkAUiT0Q+LAKeImpI+IOQ/gP2w6HstYKivpFIpBPIvZ/9equM3icVrn1iDfDkZANVXQ1PppcO3xBv1ZTw==}
dependencies:
eventemitter3: 4.0.7
gl-matrix: 3.4.3
inversify: 5.1.1
inversify-inject-decorators: 3.1.0
probe.gl: 3.5.0
reflect-metadata: 0.1.13
dev: false
/@antv/g-webgpu-engine/0.5.6:
resolution: {integrity: sha512-D311qYUefdEFwLayutIHqucrAY3cAGH3BdnXS37nq+0nsglrHcNP0Ab1YTinn9RihLoY3yXFTLzrYkJHJbZXDg==}
dependencies:
'@antv/g-webgpu-core': 0.5.6
'@webgpu/glslang': 0.0.15
'@webgpu/types': 0.0.31
gl-matrix: 3.4.3
hammerjs: 2.0.8
inversify: 5.1.1
inversify-inject-decorators: 3.1.0
probe.gl: 3.5.0
reflect-metadata: 0.1.13
regl: 1.7.0
dev: false
/@antv/g-webgpu/0.5.5:
resolution: {integrity: sha512-TxtBniINFq1jFGEPo46xjJfrbJbUqkFd5wmsRs3tcg/7J7xoldOP1kEadpI3AJG9knMYdE92VpILw1VPd6DgzQ==}
dependencies:
'@antv/g-webgpu-core': 0.5.6
'@antv/g-webgpu-engine': 0.5.6
'@webgpu/types': 0.0.31
gl-matrix: 3.4.3
gl-vec2: 1.3.0
hammerjs: 2.0.8
inversify: 5.1.1
inversify-inject-decorators: 3.1.0
polyline-miter-util: 1.0.1
polyline-normals: 2.0.2
probe.gl: 3.5.0
reflect-metadata: 0.1.13
dev: false
/@antv/layout/0.1.31:
resolution: {integrity: sha512-iz9i19dOJGiZr5xBWI5sfG+2K3QVMNAGOBrbjWKH2RGLvGpf2TSFySidhz0siDrcQA46cDsjLmGstezQdgeGzA==}
dependencies:
'@antv/g-webgpu': 0.5.5
'@dagrejs/graphlib': 2.1.4
d3-force: 2.1.1
ml-matrix: 6.8.2
dev: false
/@antv/x6/1.29.5:
resolution: {integrity: sha512-U5gg40jo+UtzjdX/7QFenVZgGKOtDFDo60AMNGEvIEzGnixV+2zV0EBJ2f8We4Fp1ZVP0G2pm6uS7ajuuuLsvg==}
dependencies:
@ -369,6 +423,13 @@ packages:
- supports-color
dev: true
/@babel/runtime/7.17.0:
resolution: {integrity: sha512-etcO/ohMNaNA2UBdaXBBSX/3aEzFMRrVfaPv8Ptc0k+cWpWW0QFiGZ2XnVqQZI1Cf734LbPGmqBKWESfW4x/dQ==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.9
dev: false
/@babel/template/7.16.7:
resolution: {integrity: sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==}
engines: {node: '>=6.9.0'}
@ -420,6 +481,12 @@ packages:
vue: 3.2.26
dev: false
/@dagrejs/graphlib/2.1.4:
resolution: {integrity: sha512-QCg9sL4uhjn468FDEsb/S9hS2xUZSrv/+dApb1Ze5VKO96pTXKNJZ6MGhIpgWkc1TVhbVGH9/7rq/Mf8/jWicw==}
dependencies:
lodash: 4.17.21
dev: false
/@emmetio/abbreviation/2.2.2:
resolution: {integrity: sha512-TtE/dBnkTCct8+LntkqVrwqQao6EnPAs1YN3cUgxOxTaBlesBCY37ROUAVZrRlG64GNnVShdl/b70RfAI3w5lw==}
dependencies:
@ -531,6 +598,25 @@ packages:
fastq: 1.13.0
dev: true
/@probe.gl/env/3.5.0:
resolution: {integrity: sha512-YdlpZZshhyYxvWDBmZ5RIW2pTR14Pw4p9czMlt/v7F6HbFzWfAdmH7q6xVwFRYxUpQLwhWensWyv4aFysiWl4g==}
dependencies:
'@babel/runtime': 7.17.0
dev: false
/@probe.gl/log/3.5.0:
resolution: {integrity: sha512-nW/qz2X1xY08WU/TsmJP6/6IPNcaY5fS/vLjpC4ahJuE2Mezga4hGM/R2X5JWE/nkPc+BsC5GnAnD13rwAxS7g==}
dependencies:
'@babel/runtime': 7.17.0
'@probe.gl/env': 3.5.0
dev: false
/@probe.gl/stats/3.5.0:
resolution: {integrity: sha512-IH2M+F3c8HR1DTroBARePUFG7wIewumtKA0UFqx51Z7S4hKrD60wFbpMmg0AcF4FvHAXMBoC+kYi1UKW9XbAOw==}
dependencies:
'@babel/runtime': 7.17.0
dev: false
/@rollup/pluginutils/4.1.2:
resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
engines: {node: '>= 8.0.0'}
@ -916,6 +1002,14 @@ packages:
vue-demi: 0.12.1_vue@3.2.26
dev: false
/@webgpu/glslang/0.0.15:
resolution: {integrity: sha512-niT+Prh3Aff8Uf1MVBVUsaNjFj9rJAKDXuoHIKiQbB+6IUP/3J3JIhBNyZ7lDhytvXxw6ppgnwKZdDJ08UMj4Q==}
dev: false
/@webgpu/types/0.0.31:
resolution: {integrity: sha512-cvvCMSZBT4VsRNtt0lI6XQqvOIIWw6+NRUtnPUMDVDgsI4pCZColz3qzF5QcP9wIYOHEc3jssIBse8UWONKhlQ==}
dev: false
/acorn-jsx/5.3.2_acorn@8.7.0:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -1213,6 +1307,26 @@ packages:
resolution: {integrity: sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==}
dev: false
/d3-dispatch/2.0.0:
resolution: {integrity: sha512-S/m2VsXI7gAti2pBoLClFFTMOO1HTtT0j99AuXLoGFKO6deHDdnv6ZGTxSTTUTgO1zVcv82fCOtDjYK4EECmWA==}
dev: false
/d3-force/2.1.1:
resolution: {integrity: sha512-nAuHEzBqMvpFVMf9OX75d00OxvOXdxY+xECIXjW6Gv8BRrXu6gAWbv/9XKrvfJ5i5DCokDW7RYE50LRoK092ew==}
dependencies:
d3-dispatch: 2.0.0
d3-quadtree: 2.0.0
d3-timer: 2.0.0
dev: false
/d3-quadtree/2.0.0:
resolution: {integrity: sha512-b0Ed2t1UUalJpc3qXzKi+cPGxeXRr4KU9YSlocN74aTzp6R/Ud43t79yLLqxHRWZfsvWXmbDWPpoENK1K539xw==}
dev: false
/d3-timer/2.0.0:
resolution: {integrity: sha512-TO4VLh0/420Y/9dO3+f9abDEFYeCUr2WZRlxJvbp4HPTQcSylXNiL6yZa9FIUvV1yRiFufl1bszTCLDqv9PWNA==}
dev: false
/dart-sass/1.25.0:
resolution: {integrity: sha512-syNOAstJXAmvD3RifcDk3fiPMyYE2fY8so6w9gf2/wNlKpG0zyH+oiXubEYVOy1WAWkzOc72pbAxwx+3OU4JJA==}
engines: {node: '>=8.9.0'}
@ -1720,6 +1834,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/eventemitter3/4.0.7:
resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==}
dev: false
/evtd/0.2.3:
resolution: {integrity: sha512-tmiT1YUVqFjTY+BSBOAskL83xNx41iUfpvKP6Gcd/xMHjg3mnER98jXGXJyKnxCG19uPc6EhZiUC+MUyvoqCtw==}
dev: false
@ -1843,6 +1961,14 @@ packages:
has: 1.0.3
has-symbols: 1.0.2
/gl-matrix/3.4.3:
resolution: {integrity: sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==}
dev: false
/gl-vec2/1.3.0:
resolution: {integrity: sha512-YiqaAuNsheWmUV0Sa8k94kBB0D6RWjwZztyO+trEYS8KzJ6OQB/4686gdrf59wld4hHFIvaxynO3nRxpk1Ij/A==}
dev: false
/glob-parent/5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'}
@ -1896,6 +2022,11 @@ packages:
resolution: {integrity: sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==}
dev: true
/hammerjs/2.0.8:
resolution: {integrity: sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=}
engines: {node: '>=0.8.0'}
dev: false
/has-flag/3.0.0:
resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=}
engines: {node: '>=4'}
@ -2014,6 +2145,18 @@ packages:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/inversify-inject-decorators/3.1.0:
resolution: {integrity: sha512-/seBlVp5bXrLQS3DpKEmlgeZL6C7Tf/QITd+IMQrbBBGuCbxb7k3hRAWu9XSreNpFzLgSboz3sClLSEmGwHphw==}
dev: false
/inversify/5.1.1:
resolution: {integrity: sha512-j8grHGDzv1v+8T1sAQ+3boTCntFPfvxLCkNcxB1J8qA0lUN+fAlSyYd+RXKvaPRL4AGyPxViutBEJHNXOyUdFQ==}
dev: false
/is-any-array/2.0.0:
resolution: {integrity: sha512-WdPV58rT3aOWXvvyuBydnCq4S2BM1Yz8shKxlEpk/6x+GX202XRvXOycEFtNgnHVLoc46hpexPFx8Pz1/sMS0w==}
dev: false
/is-binary-path/2.1.0:
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
engines: {node: '>=8'}
@ -2286,6 +2429,32 @@ packages:
hasBin: true
dev: true
/ml-array-max/1.2.4:
resolution: {integrity: sha512-BlEeg80jI0tW6WaPyGxf5Sa4sqvcyY6lbSn5Vcv44lp1I2GR6AWojfUvLnGTNsIXrZ8uqWmo8VcG1WpkI2ONMQ==}
dependencies:
is-any-array: 2.0.0
dev: false
/ml-array-min/1.2.3:
resolution: {integrity: sha512-VcZ5f3VZ1iihtrGvgfh/q0XlMobG6GQ8FsNyQXD3T+IlstDv85g8kfV0xUG1QPRO/t21aukaJowDzMTc7j5V6Q==}
dependencies:
is-any-array: 2.0.0
dev: false
/ml-array-rescale/1.3.7:
resolution: {integrity: sha512-48NGChTouvEo9KBctDfHC3udWnQKNKEWN0ziELvY3KG25GR5cA8K8wNVzracsqSW1QEkAXjTNx+ycgAv06/1mQ==}
dependencies:
is-any-array: 2.0.0
ml-array-max: 1.2.4
ml-array-min: 1.2.3
dev: false
/ml-matrix/6.8.2:
resolution: {integrity: sha512-5o2gVLFyieDSgsStEU5mqty4MZqfeytYA/gJqBSw5/Xuob0X2UrFX/k7FDh+YAwjzG/1l8nYa0oDaJ0sGs/RlA==}
dependencies:
ml-array-rescale: 1.3.7
dev: false
/monaco-editor/0.31.1:
resolution: {integrity: sha512-FYPwxGZAeP6mRRyrr5XTGHD9gRXVjy7GUzF4IPChnyt3fS5WrNxIkS8DNujWf6EQy0Zlzpxw8oTVE+mWI2/D1Q==}
dev: false
@ -2470,6 +2639,18 @@ packages:
vue-demi: 0.12.1_vue@3.2.26
dev: false
/polyline-miter-util/1.0.1:
resolution: {integrity: sha1-tpPyOJ6g3tNqa89ezS7OS2kX2Vc=}
dependencies:
gl-vec2: 1.3.0
dev: false
/polyline-normals/2.0.2:
resolution: {integrity: sha1-oXN+ddjA3MsaWR+csn8J7vS30TU=}
dependencies:
polyline-miter-util: 1.0.1
dev: false
/postcss-filter-plugins/3.0.1:
resolution: {integrity: sha512-tRKbW4wWBEkSSFuJtamV2wkiV9rj6Yy7P3Y13+zaynlPEEZt8EgYKn3y/RBpMeIhNmHXFlSdzofml65hD5OafA==}
dependencies:
@ -2555,6 +2736,15 @@ packages:
react-is: 17.0.2
dev: false
/probe.gl/3.5.0:
resolution: {integrity: sha512-KWj8u0PNytr/rVwcQFcN7O8SK7n/ITOsUZ91l4fSX95oHhKvVCI7eadrzFUzFRlXkFfBWpMWZXFHITsHHHUctw==}
dependencies:
'@babel/runtime': 7.17.0
'@probe.gl/env': 3.5.0
'@probe.gl/log': 3.5.0
'@probe.gl/stats': 3.5.0
dev: false
/progress/2.0.3:
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
engines: {node: '>=0.4.0'}
@ -2689,11 +2879,23 @@ packages:
picomatch: 2.3.1
dev: true
/reflect-metadata/0.1.13:
resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==}
dev: false
/regenerator-runtime/0.13.9:
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
dev: false
/regexpp/3.2.0:
resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==}
engines: {node: '>=8'}
dev: true
/regl/1.7.0:
resolution: {integrity: sha512-bEAtp/qrtKucxXSJkD4ebopFZYP0q1+3Vb2WECWv/T8yQEgKxDxJ7ztO285tAMaYZVR6mM1GgI6CCn8FROtL1w==}
dev: false
/request-light/0.5.7:
resolution: {integrity: sha512-i/wKzvcx7Er8tZnvqSxWuNO5ZGggu2UgZAqj/RyZ0si7lBTXL7kZiI/dWxzxnQjaY7s5HEy1qK21Do4Ncr6cVw==}
dev: true

5
dolphinscheduler-ui-next/src/components/modal/index.tsx

@ -46,6 +46,10 @@ const props = {
confirmLoading: {
type: Boolean as PropType<boolean>,
default: false
},
autoFocus: {
type: Boolean as PropType<boolean>,
default: true
}
}
@ -75,6 +79,7 @@ const Modal = defineComponent({
v-model={[this.show, 'show']}
class={styles.container}
mask-closable={false}
auto-focus={this.autoFocus}
>
<NCard title={this.title}>
{{

10
dolphinscheduler-ui-next/src/locales/modules/en_US.ts

@ -482,15 +482,19 @@ const project = {
download_log: 'Download Log'
},
dag: {
createWorkflow: 'Create Workflow',
create: 'Create Workflow',
search: 'Search',
download_png: 'Download PNG',
fullscreen_open: 'Open Fullscreen',
fullscreen_close: 'Close Fullscreen',
workflow_version: 'Workflow Version Info',
save: 'Save',
close: 'Close',
format: 'Format'
format: 'Format',
layout_type: 'Layout Type',
grid_layout: 'Grid',
dagre_layout: 'Dagre',
rows: 'Rows',
cols: 'Cols'
}
}

10
dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts

@ -481,15 +481,19 @@ const project = {
download_log: '下载日志'
},
dag: {
createWorkflow: '创建工作流',
create: '创建工作流',
search: '搜索',
download_png: '下载工作流图片',
fullscreen_open: '全屏',
fullscreen_close: '退出全屏',
workflow_version: '工作流版本信息',
save: '保存',
close: '关闭',
format: '格式化'
format: '格式化',
layout_type: '布局类型',
grid_layout: '网格布局',
dagre_layout: '层次布局',
rows: '行数',
cols: '列数'
}
}

105
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-auto-layout-modal.tsx

@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, PropType, ref } from 'vue'
import type { Ref } from 'vue'
import Modal from '@/components/modal'
import { useI18n } from 'vue-i18n'
import {
NForm,
NFormItem,
NInputNumber,
NRadioButton,
NRadioGroup
} from 'naive-ui'
import { LAYOUT_TYPE } from './use-graph-auto-layout'
import './x6-style.scss'
const props = {
visible: {
type: Boolean as PropType<boolean>,
default: false
},
formValue: {
type: Object as PropType<Ref<any>>,
default: ref()
},
formRef: {
type: Object as PropType<Ref<any>>,
default: ref()
},
submit: {
type: Function as PropType<() => void>,
default: () => {}
},
cancel: {
type: Function as PropType<() => void>,
default: () => {}
}
}
export default defineComponent({
name: 'dag-format-modal',
props,
setup(props, context) {
const { t } = useI18n()
const { formValue, formRef, submit, cancel } = props
return () => (
<Modal
show={props.visible}
title={t('project.dag.format')}
onConfirm={submit}
onCancel={cancel}
autoFocus={false}
>
<NForm
label-width='80'
model={formValue.value}
rules={{}}
size='medium'
label-placement='left'
ref={formRef}
>
<NFormItem label={t('project.dag.layout_type')} path='type'>
<NRadioGroup
v-model={[formValue.value.type, 'value']}
name='radiogroup'
>
<NRadioButton value={LAYOUT_TYPE.GRID}>
{t('project.dag.grid_layout')}
</NRadioButton>
<NRadioButton value={LAYOUT_TYPE.DAGRE}>
{t('project.dag.dagre_layout')}
</NRadioButton>
</NRadioGroup>
</NFormItem>
{formValue.value.type === LAYOUT_TYPE.GRID ? (
<NFormItem label={t('project.dag.rows')} path='rows'>
<NInputNumber v-model={[formValue.value.rows, 'value']} min={0} />
</NFormItem>
) : null}
{formValue.value.type === LAYOUT_TYPE.GRID ? (
<NFormItem label={t('project.dag.cols')} path='cols'>
<NInputNumber v-model={[formValue.value.cols, 'value']} min={0} />
</NFormItem>
) : null}
</NForm>
</Modal>
)
}
})

2
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx

@ -18,7 +18,7 @@
import { defineComponent, ref, inject } from 'vue'
import Styles from './dag.module.scss'
import type { PropType, Ref } from 'vue'
import type { Dragged } from './dag'
import type { Dragged } from './index'
import { useCanvasInit, useCellActive, useCanvasDrop } from './dag-hooks'
import { useRoute } from 'vue-router'

35
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx

@ -1,35 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, PropType } from 'vue'
import Modal from '@/components/modal'
import './x6-style.scss'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false
}
}
export default defineComponent({
name: 'dag-format-modal',
props,
setup(props, context) {
return () => <Modal show={props.show}></Modal>
}
})

4
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts

@ -21,6 +21,7 @@ import { useCellActive } from './use-cell-active'
import { useSidebarDrag } from './use-sidebar-drag'
import { useCanvasDrop } from './use-canvas-drop'
import { useNodeSearch } from './use-node-search'
import { useGraphAutoLayout } from './use-graph-auto-layout'
export {
useCanvasInit,
@ -28,5 +29,6 @@ export {
useCellActive,
useSidebarDrag,
useCanvasDrop,
useNodeSearch
useNodeSearch,
useGraphAutoLayout
}

5
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx

@ -57,9 +57,10 @@ export default defineComponent({
onDragstart={(e) => onDragStart(e, task.type)}
>
<em
class={`${Styles['sidebar-icon']} ${
class={[
Styles['sidebar-icon'],
Styles['icon-' + task.type.toLocaleLowerCase()]
}`}
]}
></em>
<span>{task.alias}</span>
</div>

46
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx

@ -15,7 +15,7 @@
* limitations under the License.
*/
import { defineComponent, ref, inject } from 'vue'
import { defineComponent, ref, inject, PropType } from 'vue'
import { useI18n } from 'vue-i18n'
import Styles from './dag.module.scss'
import { NTooltip, NIcon, NButton, NSelect } from 'naive-ui'
@ -31,11 +31,23 @@ import { useNodeSearch } from './dag-hooks'
import { DataUri } from '@antv/x6'
import { useFullscreen } from '@vueuse/core'
import { useRouter } from 'vue-router'
import { useThemeStore } from '@/store/theme/theme'
const props = {
layoutToggle: {
type: Function as PropType<(bool?: boolean) => void>,
default: () => {}
}
}
export default defineComponent({
name: 'workflow-dag-toolbar',
props,
setup(props, context) {
const { t } = useI18n()
const themeStore = useThemeStore()
const graph = inject('graph', ref())
const router = useRouter()
@ -88,11 +100,8 @@ export default defineComponent({
/**
* Open DAG format modal
*/
const { openFormatModal } = inject('formatModal', {
openFormatModal: (bool: boolean) => {}
})
const onFormat = () => {
openFormatModal(true)
props.layoutToggle(true)
}
/**
@ -103,10 +112,13 @@ export default defineComponent({
}
return () => (
<div class={Styles.toolbar}>
<span class={Styles['workflow-name']}>
{t('project.workflow.create_workflow')}
</span>
<div
class={[
Styles.toolbar,
Styles[themeStore.darkTheme ? 'toolbar-dark' : 'toolbar-light']
]}
>
<span class={Styles['workflow-name']}>{t('project.dag.create')}</span>
<div class={Styles['toolbar-right-part']}>
{/* Search node */}
<NTooltip
@ -128,7 +140,7 @@ export default defineComponent({
}}
/>
),
default: () => t('project.workflow.search')
default: () => t('project.dag.search')
}}
></NTooltip>
<div
@ -164,7 +176,7 @@ export default defineComponent({
}}
/>
),
default: () => t('project.workflow.download_png')
default: () => t('project.dag.download_png')
}}
></NTooltip>
{/* Toggle fullscreen */}
@ -193,8 +205,8 @@ export default defineComponent({
),
default: () =>
isFullscreen.value
? t('project.workflow.fullscreen_close')
: t('project.workflow.fullscreen_open')
? t('project.dag.fullscreen_close')
: t('project.dag.fullscreen_open')
}}
></NTooltip>
{/* DAG Format */}
@ -217,7 +229,7 @@ export default defineComponent({
}}
/>
),
default: () => t('project.workflow.format')
default: () => t('project.dag.format')
}}
></NTooltip>
{/* Version info */}
@ -240,7 +252,7 @@ export default defineComponent({
}}
/>
),
default: () => t('project.workflow.workflow_version')
default: () => t('project.workflow.version_info')
}}
></NTooltip>
{/* Save workflow */}
@ -250,11 +262,11 @@ export default defineComponent({
secondary
round
>
{t('project.workflow.save')}
{t('project.dag.save')}
</NButton>
{/* Return to previous page */}
<NButton secondary round onClick={onClose}>
{t('project.workflow.close')}
{t('project.dag.close')}
</NButton>
</div>
</div>

100
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss

@ -19,8 +19,14 @@ $blue: #288fff;
$blueBg: rgba(40, 143, 255, 0.1);
$toolbarHeight: 50px;
$borderDark: rgba(255, 255, 255, 0.09);
$borderLight: rgb(239, 239, 245);
$bgDark: rgb(24, 24, 28);
$bgLight: #ffffff;
.dag {
height: 100%;
overflow: hidden;
}
.content {
@ -34,17 +40,25 @@ $toolbarHeight: 50px;
display: flex;
align-items: center;
padding: 0 20px;
border: 1px solid var(--n-border-color);
border-radius: 4px;
justify-content: space-between;
}
.dag-dark .toolbar {
border: 1px solid $borderDark;
}
.dag-light .toolbar-light {
border: 1px solid $borderLight;
}
.canvas {
width: 100%;
height: 100%;
position: relative;
overflow: hidden;
display: flex;
flex: 1;
}
.paper {
@ -68,7 +82,6 @@ $toolbarHeight: 50px;
height: 32px;
margin-bottom: 10px;
align-items: center;
border: 1px solid var(--n-border-color);
padding: 0 10px;
border-radius: 4px;
transform: translate(0, 0);
@ -83,112 +96,125 @@ $toolbarHeight: 50px;
background-size: 100% 100%;
margin-right: 10px;
&.icon-shell {
background-image: url('@/assets/images/task-icons/shell.png');
background-image: url("@/assets/images/task-icons/shell.png");
}
&.icon-sub_process {
background-image: url('@/assets/images/task-icons/sub_process.png');
background-image: url("@/assets/images/task-icons/sub_process.png");
}
&.icon-procedure {
background-image: url('@/assets/images/task-icons/procedure.png');
background-image: url("@/assets/images/task-icons/procedure.png");
}
&.icon-sql {
background-image: url('@/assets/images/task-icons/sql.png');
background-image: url("@/assets/images/task-icons/sql.png");
}
&.icon-flink {
background-image: url('@/assets/images/task-icons/flink.png');
background-image: url("@/assets/images/task-icons/flink.png");
}
&.icon-mr {
background-image: url('@/assets/images/task-icons/mr.png');
background-image: url("@/assets/images/task-icons/mr.png");
}
&.icon-python {
background-image: url('@/assets/images/task-icons/python.png');
background-image: url("@/assets/images/task-icons/python.png");
}
&.icon-dependent {
background-image: url('@/assets/images/task-icons/dependent.png');
background-image: url("@/assets/images/task-icons/dependent.png");
}
&.icon-http {
background-image: url('@/assets/images/task-icons/http.png');
background-image: url("@/assets/images/task-icons/http.png");
}
&.icon-datax {
background-image: url('@/assets/images/task-icons/datax.png');
background-image: url("@/assets/images/task-icons/datax.png");
}
&.icon-pigeon {
background-image: url('@/assets/images/task-icons/pigeon.png');
background-image: url("@/assets/images/task-icons/pigeon.png");
}
&.icon-sqoop {
background-image: url('@/assets/images/task-icons/sqoop.png');
background-image: url("@/assets/images/task-icons/sqoop.png");
}
&.icon-conditions {
background-image: url('@/assets/images/task-icons/conditions.png');
background-image: url("@/assets/images/task-icons/conditions.png");
}
&.icon-seatunnel {
background-image: url('@/assets/images/task-icons/seatunnel.png');
background-image: url("@/assets/images/task-icons/seatunnel.png");
}
&.icon-spark {
background-image: url('@/assets/images/task-icons/spark.png');
background-image: url("@/assets/images/task-icons/spark.png");
}
&.icon-switch {
background-image: url('@/assets/images/task-icons/switch.png');
background-image: url("@/assets/images/task-icons/switch.png");
}
}
&:hover {
color: $blue;
border: 1px dashed $blue;
background-color: $blueBg;
.sidebar-icon {
&.icon-shell {
background-image: url('@/assets/images/task-icons/shell_hover.png');
background-image: url("@/assets/images/task-icons/shell_hover.png");
}
&.icon-sub_process {
background-image: url('@/assets/images/task-icons/sub_process_hover.png');
background-image: url("@/assets/images/task-icons/sub_process_hover.png");
}
&.icon-procedure {
background-image: url('@/assets/images/task-icons/procedure_hover.png');
background-image: url("@/assets/images/task-icons/procedure_hover.png");
}
&.icon-sql {
background-image: url('@/assets/images/task-icons/sql_hover.png');
background-image: url("@/assets/images/task-icons/sql_hover.png");
}
&.icon-flink {
background-image: url('@/assets/images/task-icons/flink_hover.png');
background-image: url("@/assets/images/task-icons/flink_hover.png");
}
&.icon-mr {
background-image: url('@/assets/images/task-icons/mr_hover.png');
background-image: url("@/assets/images/task-icons/mr_hover.png");
}
&.icon-python {
background-image: url('@/assets/images/task-icons/python_hover.png');
background-image: url("@/assets/images/task-icons/python_hover.png");
}
&.icon-dependent {
background-image: url('@/assets/images/task-icons/dependent_hover.png');
background-image: url("@/assets/images/task-icons/dependent_hover.png");
}
&.icon-http {
background-image: url('@/assets/images/task-icons/http_hover.png');
background-image: url("@/assets/images/task-icons/http_hover.png");
}
&.icon-datax {
background-image: url('@/assets/images/task-icons/datax_hover.png');
background-image: url("@/assets/images/task-icons/datax_hover.png");
}
&.icon-pigeon {
background-image: url('@/assets/images/task-icons/pigeon_hover.png');
background-image: url("@/assets/images/task-icons/pigeon_hover.png");
}
&.icon-sqoop {
background-image: url('@/assets/images/task-icons/sqoop_hover.png');
background-image: url("@/assets/images/task-icons/sqoop_hover.png");
}
&.icon-conditions {
background-image: url('@/assets/images/task-icons/conditions_hover.png');
background-image: url("@/assets/images/task-icons/conditions_hover.png");
}
&.icon-seatunnel {
background-image: url('@/assets/images/task-icons/seatunnel_hover.png');
background-image: url("@/assets/images/task-icons/seatunnel_hover.png");
}
&.icon-spark {
background-image: url('@/assets/images/task-icons/spark_hover.png');
background-image: url("@/assets/images/task-icons/spark_hover.png");
}
&.icon-switch {
background-image: url('@/assets/images/task-icons/switch_hover.png');
background-image: url("@/assets/images/task-icons/switch_hover.png");
}
}
}
}
.dag-dark .draggable {
border: 1px solid $borderDark;
}
.dag-light .draggable {
border: 1px solid $borderLight;
}
.dag .draggable {
&:hover {
color: $blue;
border: 1px dashed $blue;
background-color: $blueBg;
}
}
.minimap {
position: absolute;
right: 0px;

41
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx

@ -21,7 +21,9 @@ import DagToolbar from './dag-toolbar'
import DagCanvas from './dag-canvas'
import DagSidebar from './dag-sidebar'
import Styles from './dag.module.scss'
import DagFormatModal from './dag-format-modal'
import DagAutoLayoutModal from './dag-auto-layout-modal'
import { useGraphAutoLayout } from './dag-hooks'
import { useThemeStore } from '@/store/theme/theme'
import './x6-style.scss'
export interface Dragged {
@ -33,6 +35,8 @@ export interface Dragged {
export default defineComponent({
name: 'workflow-dag',
setup(props, context) {
const theme = useThemeStore()
// Whether the graph can be operated
const readonly = ref(false)
provide('readonly', readonly)
@ -53,24 +57,35 @@ export default defineComponent({
type: ''
})
// Dag format modal visible
const formatModalVisible = ref<boolean>(false)
const openFormatModal = (bool: boolean) => {
formatModalVisible.value = bool
}
provide('formatModal', {
openFormatModal,
formatModalVisible
})
// Auto layout
const {
visible: layoutVisible,
toggle: layoutToggle,
formValue,
formRef,
submit,
cancel
} = useGraphAutoLayout({ graph })
return () => (
<div class={Styles.dag}>
<DagToolbar v-slots={toolbarSlots} />
<div
class={[
Styles.dag,
Styles[`dag-${theme.darkTheme ? 'dark' : 'light'}`]
]}
>
<DagToolbar v-slots={toolbarSlots} layoutToggle={layoutToggle} />
<div class={Styles.content}>
<DagSidebar dragged={dragged} />
<DagCanvas dragged={dragged} />
</div>
<DagFormatModal show={formatModalVisible.value} />
<DagAutoLayoutModal
visible={layoutVisible.value}
submit={submit}
cancel={cancel}
formValue={formValue}
formRef={formRef}
/>
</div>
)
}

19
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts

@ -20,6 +20,7 @@ import { ref, onMounted, Ref, onUnmounted } from 'vue'
import { Graph } from '@antv/x6'
import { NODE, EDGE, X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
import { debounce } from 'lodash'
import { useResizeObserver } from '@vueuse/core'
interface Options {
readonly: Ref<boolean>
@ -136,18 +137,14 @@ export function useCanvasInit(options: Options) {
/**
* Redraw when the page is resized
*/
const paperResize = debounce(() => {
if (!container.value) return
const w = container.value.offsetWidth
const h = container.value.offsetHeight
graph.value?.resize(w, h)
const resize = debounce(() => {
if (container.value && true) {
const w = container.value.offsetWidth
const h = container.value.offsetHeight
graph.value?.resize(w, h)
}
}, 200)
onMounted(() => {
window.addEventListener('resize', paperResize)
})
onUnmounted(() => {
window.removeEventListener('resize', paperResize)
})
useResizeObserver(container, resize)
/**
* Register custom cells

177
dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-auto-layout.ts

@ -0,0 +1,177 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ref, Ref } from 'vue'
import type { Graph } from '@antv/x6'
import { DagreLayout, GridLayout } from '@antv/layout'
import _ from 'lodash'
import { X6_NODE_NAME, X6_EDGE_NAME } from './dag-config'
interface Options {
graph: Ref<Graph | undefined>
}
interface LayoutConfig {
cols: number
nodesep: number
padding: number
ranksep: number
rows: number
type: LAYOUT_TYPE
}
export enum LAYOUT_TYPE {
GRID = 'grid',
DAGRE = 'dagre'
}
/**
* Auto layout graph
* 1. Manage the state of auto layout popups
* 2. Implement graph automatic layout function
*/
export function useGraphAutoLayout(options: Options) {
const DEFAULT_LAYOUT_CONFIG: LayoutConfig = {
cols: 0,
nodesep: 50,
padding: 50,
ranksep: 50,
rows: 0,
type: LAYOUT_TYPE.DAGRE
}
const { graph: graphRef } = options
// Auto layout config form ref
const formRef = ref()
// Auto layout config form value
const formValue = ref({
...DEFAULT_LAYOUT_CONFIG
})
// Dag format modal visible
const visible = ref<boolean>(false)
const toggle = (bool?: boolean) => {
if (typeof bool === 'boolean') {
visible.value = bool
} else {
visible.value = !visible.value
}
}
/**
* Auto layout graph
* @param layoutConfig
* @returns
*/
function format(layoutConfig: LayoutConfig) {
if (!layoutConfig) {
layoutConfig = DEFAULT_LAYOUT_CONFIG
}
const graph = graphRef?.value
if (!graph) {
return
}
graph.cleanSelection()
let layoutFunc = null
if (layoutConfig.type === LAYOUT_TYPE.DAGRE) {
layoutFunc = new DagreLayout({
type: LAYOUT_TYPE.DAGRE,
rankdir: 'LR',
align: 'UL',
// Calculate the node spacing based on the edge label length
ranksepFunc: (d) => {
const edges = graph.getOutgoingEdges(d.id)
let max = 0
if (edges && edges.length > 0) {
edges.forEach((edge) => {
const edgeView = graph.findViewByCell(edge)
const labelView = edgeView?.findAttr(
'width',
_.get(edgeView, ['labelSelectors', '0', 'body'], null)
)
const labelWidth = labelView ? +labelView : 0
max = Math.max(max, labelWidth)
})
}
return layoutConfig.ranksep + max
},
nodesep: layoutConfig.nodesep,
controlPoints: true
})
} else if (layoutConfig.type === LAYOUT_TYPE.GRID) {
layoutFunc = new GridLayout({
type: LAYOUT_TYPE.GRID,
preventOverlap: true,
preventOverlapPadding: layoutConfig.padding,
sortBy: '_index',
rows: layoutConfig.rows || undefined,
cols: layoutConfig.cols || undefined,
nodeSize: 220
})
}
const json = graph.toJSON()
const nodes = json.cells
.filter((cell) => cell.shape === X6_NODE_NAME)
.map((item) => {
return {
...item,
// sort by code aesc
_index: -(item.id as string)
}
})
const edges = json.cells.filter((cell) => cell.shape === X6_EDGE_NAME)
const newModel: any = layoutFunc?.layout({
nodes: nodes,
edges: edges
} as any)
graph.fromJSON(newModel)
}
/**
* Auto layout modal submit
*/
function submit() {
if (formRef.value) {
formRef.value.validate((errors: unknown) => {
if (errors) return
format(formValue.value)
toggle(false)
})
}
}
/**
* Auto layout modal cancel
*/
function cancel() {
toggle(false)
}
return {
format,
toggle,
visible,
formRef,
formValue,
cancel,
submit
}
}

18
dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.module.scss

@ -15,8 +15,26 @@
* limitations under the License.
*/
$borderDark: rgba(255, 255, 255, 0.09);
$borderLight: rgb(239, 239, 245);
$bgDark: rgb(24, 24, 28);
$bgLight: #ffffff;
.container {
width: 100%;
padding: 20px;
box-sizing: border-box;
height: calc(100vh - 100px);
overflow: hidden;
display: block;
}
.dark {
border: solid 1px $borderDark;
background-color: $bgDark;
}
.light {
border: solid 1px $borderLight;
background-color: $bgLight;
}

15
dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx

@ -17,21 +17,28 @@
import { defineComponent } from 'vue'
import Dag from '../../components/dag'
import { NCard } from 'naive-ui'
import styles from './index.module.scss'
import { useThemeStore } from '@/store/theme/theme'
import Styles from './index.module.scss'
export default defineComponent({
name: 'WorkflowDefinitionCreate',
setup() {
const theme = useThemeStore()
const slots = {
toolbarLeft: () => <span>left-operations</span>,
toolbarRight: () => <span>right-operations</span>
}
return () => (
<NCard class={styles.container}>
<div
class={[
Styles.container,
theme.darkTheme ? Styles['dark'] : Styles['light']
]}
>
<Dag v-slots={slots} />
</NCard>
</div>
)
}
})

Loading…
Cancel
Save