Browse Source

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

wangyizhi 3 years ago
parent
commit
78cac08532

+ 2 - 1
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 - 0
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 - 0
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}>
           {{

+ 7 - 3
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'
   }
 }
 

+ 7 - 3
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 - 0
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>
+    )
+  }
+})

+ 1 - 1
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'
 

+ 0 - 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>
-  }
-})

+ 3 - 1
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
 }

+ 3 - 2
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>

+ 29 - 17
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>

+ 63 - 37
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;

+ 28 - 13
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>
     )
   }

+ 8 - 11
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 - 0
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 - 0
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;
 }

+ 11 - 4
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>
     )
   }
 })