From 78cac085323b1a28d866ed10357c89f2122d06a3 Mon Sep 17 00:00:00 2001 From: wangyizhi Date: Mon, 7 Feb 2022 22:05:36 +0800 Subject: [PATCH] [Feature][UI Next] Auto layout modal (#8298) --- dolphinscheduler-ui-next/package.json | 3 +- dolphinscheduler-ui-next/pnpm-lock.yaml | 202 ++++++++++++++++++ .../src/components/modal/index.tsx | 5 + .../src/locales/modules/en_US.ts | 10 +- .../src/locales/modules/zh_CN.ts | 10 +- .../components/dag/dag-auto-layout-modal.tsx | 105 +++++++++ .../workflow/components/dag/dag-canvas.tsx | 2 +- .../components/dag/dag-format-modal.tsx | 35 --- .../workflow/components/dag/dag-hooks.ts | 4 +- .../workflow/components/dag/dag-sidebar.tsx | 5 +- .../workflow/components/dag/dag-toolbar.tsx | 46 ++-- .../workflow/components/dag/dag.module.scss | 162 ++++++++------ .../workflow/components/dag/index.tsx | 41 ++-- .../components/dag/use-canvas-init.ts | 19 +- .../components/dag/use-graph-auto-layout.ts | 177 +++++++++++++++ .../definition/create/index.module.scss | 18 ++ .../workflow/definition/create/index.tsx | 15 +- 17 files changed, 700 insertions(+), 159 deletions(-) create mode 100644 dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-auto-layout-modal.tsx delete mode 100644 dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx create mode 100644 dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-auto-layout.ts diff --git a/dolphinscheduler-ui-next/package.json b/dolphinscheduler-ui-next/package.json index 948e7c3889..a0b4ce128c 100644 --- a/dolphinscheduler-ui-next/package.json +++ b/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", diff --git a/dolphinscheduler-ui-next/pnpm-lock.yaml b/dolphinscheduler-ui-next/pnpm-lock.yaml index bab0b6117d..d0f19e8b04 100644 --- a/dolphinscheduler-ui-next/pnpm-lock.yaml +++ b/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 diff --git a/dolphinscheduler-ui-next/src/components/modal/index.tsx b/dolphinscheduler-ui-next/src/components/modal/index.tsx index c8a09b8e0a..a5e2769f1f 100644 --- a/dolphinscheduler-ui-next/src/components/modal/index.tsx +++ b/dolphinscheduler-ui-next/src/components/modal/index.tsx @@ -46,6 +46,10 @@ const props = { confirmLoading: { type: Boolean as PropType, default: false + }, + autoFocus: { + type: Boolean as PropType, + default: true } } @@ -75,6 +79,7 @@ const Modal = defineComponent({ v-model={[this.show, 'show']} class={styles.container} mask-closable={false} + auto-focus={this.autoFocus} > {{ diff --git a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts b/dolphinscheduler-ui-next/src/locales/modules/en_US.ts index 240ee14088..1e69969ca1 100644 --- a/dolphinscheduler-ui-next/src/locales/modules/en_US.ts +++ b/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' } } diff --git a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts b/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts index 2aa8926820..e0f59205ad 100644 --- a/dolphinscheduler-ui-next/src/locales/modules/zh_CN.ts +++ b/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: '列数' } } diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-auto-layout-modal.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-auto-layout-modal.tsx new file mode 100644 index 0000000000..2beea211f5 --- /dev/null +++ b/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, + default: false + }, + formValue: { + type: Object as PropType>, + default: ref() + }, + formRef: { + type: Object as PropType>, + 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 () => ( + + + + + + {t('project.dag.grid_layout')} + + + {t('project.dag.dagre_layout')} + + + + {formValue.value.type === LAYOUT_TYPE.GRID ? ( + + + + ) : null} + {formValue.value.type === LAYOUT_TYPE.GRID ? ( + + + + ) : null} + + + ) + } +}) diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx index 5aaa962a6d..221bc8e5be 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-canvas.tsx +++ b/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' diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx deleted file mode 100644 index 9efc269c4f..0000000000 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-format-modal.tsx +++ /dev/null @@ -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, - default: false - } -} - -export default defineComponent({ - name: 'dag-format-modal', - props, - setup(props, context) { - return () => - } -}) diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts index f9054d0f3e..a2d57e265f 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-hooks.ts +++ b/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 } diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx index 1a56970cf2..420d4eb523 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-sidebar.tsx +++ b/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)} > {task.alias} diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx index d514700b4f..b1081ce1d3 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag-toolbar.tsx +++ b/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 () => ( -
- - {t('project.workflow.create_workflow')} - +
+ {t('project.dag.create')}
{/* Search node */} ), - default: () => t('project.workflow.search') + default: () => t('project.dag.search') }} >
), - default: () => t('project.workflow.download_png') + default: () => t('project.dag.download_png') }} > {/* 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') }} > {/* DAG Format */} @@ -217,7 +229,7 @@ export default defineComponent({ }} /> ), - default: () => t('project.workflow.format') + default: () => t('project.dag.format') }} > {/* Version info */} @@ -240,7 +252,7 @@ export default defineComponent({ }} /> ), - default: () => t('project.workflow.workflow_version') + default: () => t('project.workflow.version_info') }} > {/* Save workflow */} @@ -250,11 +262,11 @@ export default defineComponent({ secondary round > - {t('project.workflow.save')} + {t('project.dag.save')} {/* Return to previous page */} - {t('project.workflow.close')} + {t('project.dag.close')}
diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss index dda1ed9446..52e0edb1c0 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/dag.module.scss +++ b/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,109 +96,122 @@ $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 { + .sidebar-icon { + &.icon-shell { + background-image: url("@/assets/images/task-icons/shell_hover.png"); + } + &.icon-sub_process { + background-image: url("@/assets/images/task-icons/sub_process_hover.png"); + } + &.icon-procedure { + background-image: url("@/assets/images/task-icons/procedure_hover.png"); + } + &.icon-sql { + background-image: url("@/assets/images/task-icons/sql_hover.png"); + } + &.icon-flink { + background-image: url("@/assets/images/task-icons/flink_hover.png"); + } + &.icon-mr { + background-image: url("@/assets/images/task-icons/mr_hover.png"); + } + &.icon-python { + background-image: url("@/assets/images/task-icons/python_hover.png"); + } + &.icon-dependent { + background-image: url("@/assets/images/task-icons/dependent_hover.png"); + } + &.icon-http { + background-image: url("@/assets/images/task-icons/http_hover.png"); + } + &.icon-datax { + background-image: url("@/assets/images/task-icons/datax_hover.png"); + } + &.icon-pigeon { + background-image: url("@/assets/images/task-icons/pigeon_hover.png"); + } + &.icon-sqoop { + background-image: url("@/assets/images/task-icons/sqoop_hover.png"); + } + &.icon-conditions { + background-image: url("@/assets/images/task-icons/conditions_hover.png"); + } + &.icon-seatunnel { + background-image: url("@/assets/images/task-icons/seatunnel_hover.png"); + } + &.icon-spark { + background-image: url("@/assets/images/task-icons/spark_hover.png"); + } + &.icon-switch { + 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; - .sidebar-icon { - &.icon-shell { - background-image: url('@/assets/images/task-icons/shell_hover.png'); - } - &.icon-sub_process { - background-image: url('@/assets/images/task-icons/sub_process_hover.png'); - } - &.icon-procedure { - background-image: url('@/assets/images/task-icons/procedure_hover.png'); - } - &.icon-sql { - background-image: url('@/assets/images/task-icons/sql_hover.png'); - } - &.icon-flink { - background-image: url('@/assets/images/task-icons/flink_hover.png'); - } - &.icon-mr { - background-image: url('@/assets/images/task-icons/mr_hover.png'); - } - &.icon-python { - background-image: url('@/assets/images/task-icons/python_hover.png'); - } - &.icon-dependent { - background-image: url('@/assets/images/task-icons/dependent_hover.png'); - } - &.icon-http { - background-image: url('@/assets/images/task-icons/http_hover.png'); - } - &.icon-datax { - background-image: url('@/assets/images/task-icons/datax_hover.png'); - } - &.icon-pigeon { - background-image: url('@/assets/images/task-icons/pigeon_hover.png'); - } - &.icon-sqoop { - background-image: url('@/assets/images/task-icons/sqoop_hover.png'); - } - &.icon-conditions { - background-image: url('@/assets/images/task-icons/conditions_hover.png'); - } - &.icon-seatunnel { - background-image: url('@/assets/images/task-icons/seatunnel_hover.png'); - } - &.icon-spark { - background-image: url('@/assets/images/task-icons/spark_hover.png'); - } - &.icon-switch { - background-image: url('@/assets/images/task-icons/switch_hover.png'); - } - } } } diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx index 185094a9a6..fc4665e1c0 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/index.tsx +++ b/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(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 () => ( -
- +
+
- +
) } diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts index 01ea5dafc5..8ec8d12085 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-canvas-init.ts +++ b/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 @@ -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 diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-auto-layout.ts b/dolphinscheduler-ui-next/src/views/projects/workflow/components/dag/use-graph-auto-layout.ts new file mode 100644 index 0000000000..de3301829c --- /dev/null +++ b/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 +} + +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(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 + } +} diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.module.scss b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.module.scss index b3d589b5ba..38be4c83e8 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.module.scss +++ b/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; } diff --git a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx b/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx index 101cfcf326..7e2de1b69d 100644 --- a/dolphinscheduler-ui-next/src/views/projects/workflow/definition/create/index.tsx +++ b/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: () => left-operations, toolbarRight: () => right-operations } return () => ( - +
- +
) } })