From 9307c9066a955ae7cc7041f44a8112bcf0314e58 Mon Sep 17 00:00:00 2001 From: Braks <78412429+bcakmakoglu@users.noreply.github.com> Date: Fri, 15 Oct 2021 20:26:36 +0200 Subject: [PATCH] feat: script setup style * Replace jsx with script setup syntax * Simplify logic and make it more "composable" * Move Zoom functions to useZoom composable * Update eslint config & tsconfig --- .eslintrc.js | 41 +- .prettierrc | 4 +- package.json | 2 + pnpm-lock.yaml | 774 ++++++++++++++++++++++++++++ src/auto-imports.d.ts | 12 + src/composables/useZoom.ts | 187 +++++++ src/container/EdgeRenderer.vue | 49 ++ src/container/EdgeRenderer/utils.ts | 28 +- src/container/Flow.vue | 215 ++++++++ src/container/Marker.vue | 21 + src/container/MarkerDefinitions.vue | 35 ++ src/container/NodeRenderer.vue | 74 +++ src/container/NodeRenderer/utils.ts | 12 +- src/container/SelectionPane.vue | 46 ++ src/container/ZoomPane.vue | 54 ++ src/hooks/RevueFlowHooks.ts | 6 +- src/hooks/useGlobalKeyHandler.ts | 22 +- src/hooks/useKeyPress.ts | 18 +- src/hooks/useResizeHandler.ts | 37 +- src/hooks/useZoomPanHelper.ts | 143 +++-- src/store/configure-store.ts | 48 +- src/store/index.ts | 8 +- src/types/edge.ts | 4 +- src/types/node.ts | 4 +- src/types/types.ts | 45 +- src/utils/graph.ts | 131 +++-- src/utils/index.ts | 6 +- tsconfig.json | 2 +- 28 files changed, 1732 insertions(+), 296 deletions(-) create mode 100644 src/composables/useZoom.ts create mode 100644 src/container/EdgeRenderer.vue create mode 100644 src/container/Flow.vue create mode 100644 src/container/Marker.vue create mode 100644 src/container/MarkerDefinitions.vue create mode 100644 src/container/NodeRenderer.vue create mode 100644 src/container/SelectionPane.vue create mode 100644 src/container/ZoomPane.vue diff --git a/.eslintrc.js b/.eslintrc.js index 54d3f3ec..b8b6b17b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,42 +1,9 @@ const baseRules = { - '@typescript-eslint/no-explicit-any': 0, - '@typescript-eslint/ban-ts-ignore': 0, - '@typescript-eslint/ban-ts-comment': 0, - '@typescript-eslint/no-empty-function': 0, - '@typescript-eslint/explicit-module-boundary-types': 0, - 'no-use-before-define': 0, - 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['warn'], - indent: ['warn', 2, { SwitchCase: 1, flatTernaryExpressions: true }], - quotes: ['error', 'single', { avoidEscape: true }], - 'import/no-mutable-exports': 0, - 'no-cond-assign': [2], - 'no-console': 'off', - camelcase: 0, - '@typescript/camelcase': 0, - 'vue/no-v-html': 'off', - 'vue/attribute-hyphenation': 0, - 'no-undef': 'off', - 'prettier/prettier': ['error', {}, { usePrettierrc: true }] + 'prettier/prettier': ['error', {}, { usePrettierrc: true }], } module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module', - ecmaFeatures: { - jsx: true - } - }, - env: { - node: true, - commonjs: true, - es6: true, - browser: true - }, - extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', '@vue/typescript/recommended', 'plugin:prettier/recommended'], - plugins: [], - rules: baseRules + extends: ['@antfu', 'plugin:prettier/recommended'], + plugins: ['prettier'], + rules: baseRules, } diff --git a/.prettierrc b/.prettierrc index de63788e..02018a37 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,8 @@ { "singleQuote": true, - "trailingComma": "none", + "trailingComma": "all", "semi": false, + "quoteProps": "consistent", "bracketSpacing": true, - "vueIndentScriptAndStyle": false, "printWidth": 130 } diff --git a/package.json b/package.json index 3b5b4b22..6d85aa4d 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "pinia": "^2.0.0-rc.13" }, "devDependencies": { + "@antfu/eslint-config": "^0.9.0", "@babel/core": "^7.15.8", "@rollup/plugin-babel": "^5.3.0", "@rollup/plugin-commonjs": "^19.0.2", @@ -79,6 +80,7 @@ "vite": "^2.6.7", "vite-svg-loader": "^2.2.0", "vue": "^3.2.20", + "vue-eslint-parser": "^7.11.0", "vue-router": "^4.0.12", "vue-tsc": "^0.2.3" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1012619d..67c6c91a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,6 +1,7 @@ lockfileVersion: 5.3 specifiers: + '@antfu/eslint-config': ^0.9.0 '@babel/core': ^7.15.8 '@braks/revue-draggable': ^0.2.9 '@rollup/plugin-babel': ^5.3.0 @@ -43,6 +44,7 @@ specifiers: vite: ^2.6.7 vite-svg-loader: ^2.2.0 vue: ^3.2.20 + vue-eslint-parser: ^7.11.0 vue-router: ^4.0.12 vue-tsc: ^0.2.3 @@ -57,6 +59,7 @@ dependencies: pinia: 2.0.0-rc.13_typescript@4.4.4+vue@3.2.20 devDependencies: + '@antfu/eslint-config': 0.9.0_eslint@7.32.0+typescript@4.4.4 '@babel/core': 7.15.8 '@rollup/plugin-babel': 5.3.0_@babel+core@7.15.8+rollup@2.58.0 '@rollup/plugin-commonjs': 19.0.2_rollup@2.58.0 @@ -91,11 +94,101 @@ devDependencies: vite: 2.6.7 vite-svg-loader: 2.2.0 vue: 3.2.20 + vue-eslint-parser: 7.11.0_eslint@7.32.0 vue-router: 4.0.12_vue@3.2.20 vue-tsc: 0.2.3_typescript@4.4.4 packages: + /@antfu/eslint-config-basic/0.9.0_eslint@7.32.0: + resolution: {integrity: sha512-jiNycDenS19pppDvaa0oO3wWr4WDIog5Dz6WernBk+FsCyC7UzPfnG1KB3L/sovNVBB80wLZ7anGXZn0gKEHrQ==} + peerDependencies: + eslint: '>=7.4.0' + dependencies: + eslint: 7.32.0 + eslint-config-standard: 16.0.3_84f1429610a142ee8dd75a0647bfa930 + eslint-plugin-eslint-comments: 3.2.0_eslint@7.32.0 + eslint-plugin-html: 6.2.0 + eslint-plugin-import: 2.25.2_eslint@7.32.0 + eslint-plugin-jsonc: 1.7.0_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.1.0_eslint@7.32.0 + eslint-plugin-unicorn: 35.0.0_eslint@7.32.0 + eslint-plugin-yml: 0.10.1_eslint@7.32.0 + jsonc-eslint-parser: 1.4.1 + yaml-eslint-parser: 0.4.1 + transitivePeerDependencies: + - supports-color + dev: true + + /@antfu/eslint-config-react/0.9.0_eslint@7.32.0+typescript@4.4.4: + resolution: {integrity: sha512-5b4PHHamJfMTfzmcUb7rDg9oH34delky8ZmTJ+r5LLfbbPdIthYLS9Ybns5v3q6rjhhTsfHOQz3gxp9604tYbQ==} + peerDependencies: + eslint: '>=7.4.0' + dependencies: + '@antfu/eslint-config-ts': 0.9.0_eslint@7.32.0+typescript@4.4.4 + eslint: 7.32.0 + eslint-plugin-react: 7.26.1_eslint@7.32.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@antfu/eslint-config-ts/0.9.0_eslint@7.32.0+typescript@4.4.4: + resolution: {integrity: sha512-FXLLgMVjh5MpBvMteYQeQ9vkeL/cV5vig7IRoAJIpG1E5cbE+ENJt6w1YBf45PLQmoV/eZZID7cnT8ZOJ9VJfQ==} + peerDependencies: + eslint: '>=7.4.0' + typescript: '>=3.9' + dependencies: + '@antfu/eslint-config-basic': 0.9.0_eslint@7.32.0 + '@typescript-eslint/eslint-plugin': 4.33.0_cc617358c89d3f38c52462f6d809db4c + '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.4.4 + eslint: 7.32.0 + typescript: 4.4.4 + transitivePeerDependencies: + - supports-color + dev: true + + /@antfu/eslint-config-vue/0.9.0_eslint@7.32.0+typescript@4.4.4: + resolution: {integrity: sha512-h1Ab6TdcZyhQhBJIjr3CrI6U66x/izRddTQeF/OrjEyLz6HjeASbUOMgDQhmH9u47+6RP68cR6Wnjls/rlZb2g==} + peerDependencies: + eslint: '>=7.4.0' + dependencies: + '@antfu/eslint-config-ts': 0.9.0_eslint@7.32.0+typescript@4.4.4 + eslint: 7.32.0 + eslint-plugin-vue: 7.17.0_eslint@7.32.0 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@antfu/eslint-config/0.9.0_eslint@7.32.0+typescript@4.4.4: + resolution: {integrity: sha512-oQ6kNRHfF7PqZocX2+mfvY/oNdLGPjysb9huVJzMji7QtLAVL5+25Mm/kzJEIfcFWxRpDqctOcflXLYFnXiroA==} + peerDependencies: + eslint: '>=7.4.0' + dependencies: + '@antfu/eslint-config-react': 0.9.0_eslint@7.32.0+typescript@4.4.4 + '@antfu/eslint-config-vue': 0.9.0_eslint@7.32.0+typescript@4.4.4 + '@typescript-eslint/eslint-plugin': 4.33.0_cc617358c89d3f38c52462f6d809db4c + '@typescript-eslint/parser': 4.33.0_eslint@7.32.0+typescript@4.4.4 + eslint: 7.32.0 + eslint-config-standard: 16.0.3_84f1429610a142ee8dd75a0647bfa930 + eslint-plugin-eslint-comments: 3.2.0_eslint@7.32.0 + eslint-plugin-html: 6.2.0 + eslint-plugin-import: 2.25.2_eslint@7.32.0 + eslint-plugin-jsonc: 1.7.0_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.1.0_eslint@7.32.0 + eslint-plugin-unicorn: 35.0.0_eslint@7.32.0 + eslint-plugin-vue: 7.17.0_eslint@7.32.0 + eslint-plugin-yml: 0.10.1_eslint@7.32.0 + jsonc-eslint-parser: 1.4.1 + yaml-eslint-parser: 0.4.1 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + /@antfu/utils/0.3.0: resolution: {integrity: sha512-UU8TLr/EoXdg7OjMp0h9oDoIAVr+Z/oW9cpOxQQyrsz6Qzd2ms/1CdWx8fl2OQdFpxGmq5Vc4TwfLHId6nAZjA==} dependencies: @@ -143,6 +236,20 @@ packages: - supports-color dev: true + /@babel/eslint-parser/7.15.8_@babel+core@7.15.8+eslint@7.32.0: + resolution: {integrity: sha512-fYP7QFngCvgxjUuw8O057SVH5jCXsbFFOoE77CFDcvzwBVgTOkMD/L4mIC5Ud1xf8chK/no2fRbSSn1wvNmKuQ==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': '>=7.11.0' + eslint: '>=7.5.0' + dependencies: + '@babel/core': 7.15.8 + eslint: 7.32.0 + eslint-scope: 5.1.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.0 + dev: true + /@babel/generator/7.15.8: resolution: {integrity: sha512-ECmAKstXbp1cvpTTZciZCgfOt6iN64lR0d+euv3UZisU5awfRawOvg07Utn/qBGuH4bRIEZKrA/4LzZyXhZr8g==} engines: {node: '>=6.9.0'} @@ -813,6 +920,10 @@ packages: resolution: {integrity: sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==} dev: true + /@types/json5/0.0.29: + resolution: {integrity: sha1-7ihweulOEdK4J7y+UnC86n8+ce4=} + dev: true + /@types/keyv/3.1.3: resolution: {integrity: sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==} dependencies: @@ -1335,11 +1446,40 @@ packages: sprintf-js: 1.0.3 dev: true + /array-includes/3.1.4: + resolution: {integrity: sha512-ZTNSQkmWumEbiHO2GF4GmWxYVTiQyJy2XOTa15sdQSrvKn7l+180egQMqlrMOUMCyLMD7pmyQe4mMDUT6Behrw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + get-intrinsic: 1.1.1 + is-string: 1.0.7 + dev: true + /array-union/2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true + /array.prototype.flat/1.2.5: + resolution: {integrity: sha512-KaYU+S+ndVqyUnignHftkwc58o3uVU1jzczILJ1tN2YaIZpFIKBiP/x/j97E5MVPsaCloPbqWLB/8qCTVvT2qg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + + /array.prototype.flatmap/1.2.5: + resolution: {integrity: sha512-08u6rVyi1Lj7oqWbS9nUxliETrtIROT4XGTA4D/LWGten6E3ocm7cy9SIrmNHOL5XVbVuckUp3X6Xyg8/zpvHA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + /arrify/1.0.1: resolution: {integrity: sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=} engines: {node: '>=0.10.0'} @@ -1577,6 +1717,17 @@ packages: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} dev: true + /ci-info/3.2.0: + resolution: {integrity: sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==} + dev: true + + /clean-regexp/1.0.0: + resolution: {integrity: sha1-jffHquUf02h06PjQW5GAvBGj/tc=} + engines: {node: '>=4'} + dependencies: + escape-string-regexp: 1.0.5 + dev: true + /clean-stack/2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -2092,6 +2243,18 @@ packages: resolution: {integrity: sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==} dev: true + /debug/2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + dependencies: + ms: 2.0.0 + dev: true + + /debug/3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + dependencies: + ms: 2.1.2 + dev: true + /debug/4.3.2: resolution: {integrity: sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==} engines: {node: '>=6.0'} @@ -2154,6 +2317,13 @@ packages: engines: {node: '>=10'} dev: true + /define-properties/1.1.3: + resolution: {integrity: sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==} + engines: {node: '>= 0.4'} + dependencies: + object-keys: 1.1.1 + dev: true + /del/6.0.0: resolution: {integrity: sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==} engines: {node: '>=10'} @@ -2181,6 +2351,13 @@ packages: path-type: 4.0.0 dev: true + /doctrine/2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dependencies: + esutils: 2.0.3 + dev: true + /doctrine/3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} @@ -2279,12 +2456,52 @@ packages: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} dev: true + /entities/3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + dev: true + /error-ex/1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} dependencies: is-arrayish: 0.2.1 dev: true + /es-abstract/1.19.1: + resolution: {integrity: sha512-2vJ6tjA/UfqLm2MPs7jxVybLoB8i1t1Jd9R3kISld20sIxPcTbLuggQOUxeWeAvIUkduv/CfMjuh4WmiXr2v9w==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + es-to-primitive: 1.2.1 + function-bind: 1.1.1 + get-intrinsic: 1.1.1 + get-symbol-description: 1.0.0 + has: 1.0.3 + has-symbols: 1.0.2 + internal-slot: 1.0.3 + is-callable: 1.2.4 + is-negative-zero: 2.0.1 + is-regex: 1.1.4 + is-shared-array-buffer: 1.0.1 + is-string: 1.0.7 + is-weakref: 1.0.1 + object-inspect: 1.11.0 + object-keys: 1.1.1 + object.assign: 4.1.2 + string.prototype.trimend: 1.0.4 + string.prototype.trimstart: 1.0.4 + unbox-primitive: 1.0.1 + dev: true + + /es-to-primitive/1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + dependencies: + is-callable: 1.2.4 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + dev: true + /esbuild-android-arm64/0.13.6: resolution: {integrity: sha512-uEwrMRzqNzXxzIi0K/CtHn3/SPoRso4Dd/aJCpf9KuX+kCs9Tlhz29cKbZieznYAekdo36fDUrZyuugAwSdI+A==} cpu: [arm64] @@ -2492,6 +2709,113 @@ packages: eslint: 7.32.0 dev: true + /eslint-config-standard/16.0.3_84f1429610a142ee8dd75a0647bfa930: + resolution: {integrity: sha512-x4fmJL5hGqNJKGHSjnLdgA6U6h1YW/G2dW9fA+cyVur4SK6lyue8+UgNKWlZtUDTXvgKDD/Oa3GQjmB5kjtVvg==} + peerDependencies: + eslint: ^7.12.1 + eslint-plugin-import: ^2.22.1 + eslint-plugin-node: ^11.1.0 + eslint-plugin-promise: ^4.2.1 || ^5.0.0 + dependencies: + eslint: 7.32.0 + eslint-plugin-import: 2.25.2_eslint@7.32.0 + eslint-plugin-node: 11.1.0_eslint@7.32.0 + eslint-plugin-promise: 5.1.0_eslint@7.32.0 + dev: true + + /eslint-import-resolver-node/0.3.6: + resolution: {integrity: sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw==} + dependencies: + debug: 3.2.7 + resolve: 1.20.0 + dev: true + + /eslint-module-utils/2.7.1: + resolution: {integrity: sha512-fjoetBXQZq2tSTWZ9yWVl2KuFrTZZH3V+9iD1V1RfpDgxzJR+mPd/KZmMiA8gbPqdBzpNiEHOuT7IYEWxrH0zQ==} + engines: {node: '>=4'} + dependencies: + debug: 3.2.7 + find-up: 2.1.0 + pkg-dir: 2.0.0 + dev: true + + /eslint-plugin-es/3.0.1_eslint@7.32.0: + resolution: {integrity: sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + eslint: 7.32.0 + eslint-utils: 2.1.0 + regexpp: 3.2.0 + dev: true + + /eslint-plugin-eslint-comments/3.2.0_eslint@7.32.0: + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + dependencies: + escape-string-regexp: 1.0.5 + eslint: 7.32.0 + ignore: 5.1.8 + dev: true + + /eslint-plugin-html/6.2.0: + resolution: {integrity: sha512-vi3NW0E8AJombTvt8beMwkL1R/fdRWl4QSNRNMhVQKWm36/X0KF0unGNAY4mqUF06mnwVWZcIcerrCnfn9025g==} + dependencies: + htmlparser2: 7.1.2 + dev: true + + /eslint-plugin-import/2.25.2_eslint@7.32.0: + resolution: {integrity: sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + dependencies: + array-includes: 3.1.4 + array.prototype.flat: 1.2.5 + debug: 2.6.9 + doctrine: 2.1.0 + eslint: 7.32.0 + eslint-import-resolver-node: 0.3.6 + eslint-module-utils: 2.7.1 + has: 1.0.3 + is-core-module: 2.8.0 + is-glob: 4.0.3 + minimatch: 3.0.4 + object.values: 1.1.5 + resolve: 1.20.0 + tsconfig-paths: 3.11.0 + dev: true + + /eslint-plugin-jsonc/1.7.0_eslint@7.32.0: + resolution: {integrity: sha512-pb3CAD9B0zhv3r9Bg9AdzswL50I3mbIq1ys+tNeuaDeibFlweo84SBNm22oqaFx/Dka+YZw2SLukAkQlJzSHMQ==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=5.0.0' + dependencies: + eslint: 7.32.0 + eslint-utils: 3.0.0_eslint@7.32.0 + jsonc-eslint-parser: 1.4.1 + natural-compare: 1.4.0 + dev: true + + /eslint-plugin-node/11.1.0_eslint@7.32.0: + resolution: {integrity: sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==} + engines: {node: '>=8.10.0'} + peerDependencies: + eslint: '>=5.16.0' + dependencies: + eslint: 7.32.0 + eslint-plugin-es: 3.0.1_eslint@7.32.0 + eslint-utils: 2.1.0 + ignore: 5.1.8 + minimatch: 3.0.4 + resolve: 1.20.0 + semver: 6.3.0 + dev: true + /eslint-plugin-prettier/3.4.1_6e975bd57c7acf028c1a9ddbbf60c898: resolution: {integrity: sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==} engines: {node: '>=6.0.0'} @@ -2509,6 +2833,76 @@ packages: prettier-linter-helpers: 1.0.0 dev: true + /eslint-plugin-promise/5.1.0_eslint@7.32.0: + resolution: {integrity: sha512-NGmI6BH5L12pl7ScQHbg7tvtk4wPxxj8yPHH47NvSmMtFneC077PSeY3huFj06ZWZvtbfxSPt3RuOQD5XcR4ng==} + engines: {node: ^10.12.0 || >=12.0.0} + peerDependencies: + eslint: ^7.0.0 + dependencies: + eslint: 7.32.0 + dev: true + + /eslint-plugin-react/7.26.1_eslint@7.32.0: + resolution: {integrity: sha512-Lug0+NOFXeOE+ORZ5pbsh6mSKjBKXDXItUD2sQoT+5Yl0eoT82DqnXeTMfUare4QVCn9QwXbfzO/dBLjLXwVjQ==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 + dependencies: + array-includes: 3.1.4 + array.prototype.flatmap: 1.2.5 + doctrine: 2.1.0 + eslint: 7.32.0 + estraverse: 5.2.0 + jsx-ast-utils: 3.2.1 + minimatch: 3.0.4 + object.entries: 1.1.5 + object.fromentries: 2.0.5 + object.hasown: 1.1.0 + object.values: 1.1.5 + prop-types: 15.7.2 + resolve: 2.0.0-next.3 + semver: 6.3.0 + string.prototype.matchall: 4.0.6 + dev: true + + /eslint-plugin-unicorn/35.0.0_eslint@7.32.0: + resolution: {integrity: sha512-FHsaO68tDPQILfs/mGF8eSISJp8RswR4FpUuBDnueK2wyEHC6zmsc9WxjYyldXoIsBuVmru6jQyFCbCWPoW/KQ==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=7.28.0' + dependencies: + '@babel/helper-validator-identifier': 7.15.7 + ci-info: 3.2.0 + clean-regexp: 1.0.0 + eslint: 7.32.0 + eslint-template-visitor: 2.3.2_eslint@7.32.0 + eslint-utils: 3.0.0_eslint@7.32.0 + is-builtin-module: 3.1.0 + lodash: 4.17.21 + pluralize: 8.0.0 + read-pkg-up: 7.0.1 + regexp-tree: 0.1.24 + safe-regex: 2.1.1 + semver: 7.3.5 + transitivePeerDependencies: + - supports-color + dev: true + + /eslint-plugin-vue/7.17.0_eslint@7.32.0: + resolution: {integrity: sha512-Rq5R2QetDCgC+kBFQw1+aJ5B93tQ4xqZvoCUxuIzwTonngNArsdP8ChM8PowIzsJvRtWl4ltGh/bZcN3xhFWSw==} + engines: {node: '>=8.10'} + peerDependencies: + eslint: ^6.2.0 || ^7.0.0 || ^8.0.0-0 + dependencies: + eslint: 7.32.0 + eslint-utils: 2.1.0 + natural-compare: 1.4.0 + semver: 6.3.0 + vue-eslint-parser: 7.11.0_eslint@7.32.0 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-plugin-vue/7.19.1_eslint@7.32.0: resolution: {integrity: sha512-e2pD7nW2sTY04ThH+66BgToNwC4n6dqfNhKE+ypdJFtZgn3Zn+nP8ZEIFPG0PGqCKQ3qxy8dJk1bzUsuQd3ANA==} engines: {node: '>=8.10'} @@ -2524,6 +2918,20 @@ packages: - supports-color dev: true + /eslint-plugin-yml/0.10.1_eslint@7.32.0: + resolution: {integrity: sha512-af0WgO3qaH+RW6jv1s6RzXKlg2NZLisN95lqGUf1KqBT6rEJyGSCpM49QYaSTvzmMaB/gcdbrnAfNoYwUn0Yig==} + peerDependencies: + eslint: '>=6.0.0' + dependencies: + debug: 4.3.2 + eslint: 7.32.0 + lodash: 4.17.21 + natural-compare: 1.4.0 + yaml-eslint-parser: 0.4.1 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-scope/5.1.1: resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} engines: {node: '>=8.0.0'} @@ -2532,6 +2940,21 @@ packages: estraverse: 4.3.0 dev: true + /eslint-template-visitor/2.3.2_eslint@7.32.0: + resolution: {integrity: sha512-3ydhqFpuV7x1M9EK52BPNj6V0Kwu0KKkcIAfpUhwHbR8ocRln/oUHgfxQupY8O1h4Qv/POHDumb/BwwNfxbtnA==} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + '@babel/core': 7.15.8 + '@babel/eslint-parser': 7.15.8_@babel+core@7.15.8+eslint@7.32.0 + eslint: 7.32.0 + eslint-visitor-keys: 2.1.0 + esquery: 1.4.0 + multimap: 1.1.0 + transitivePeerDependencies: + - supports-color + dev: true + /eslint-utils/2.1.0: resolution: {integrity: sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==} engines: {node: '>=6'} @@ -2782,6 +3205,13 @@ packages: pkg-dir: 4.2.0 dev: true + /find-up/2.1.0: + resolution: {integrity: sha1-RdG35QbHF93UgndaK3eSCjwMV6c=} + engines: {node: '>=4'} + dependencies: + locate-path: 2.0.0 + dev: true + /find-up/4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -2881,6 +3311,14 @@ packages: engines: {node: '>=10'} dev: true + /get-symbol-description/1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + dev: true + /github-url-from-git/1.5.0: resolution: {integrity: sha1-+YX+3MCpqledyI16/waNVcxiUaA=} dev: true @@ -2995,6 +3433,10 @@ packages: ansi-regex: 2.1.1 dev: true + /has-bigints/1.0.1: + resolution: {integrity: sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==} + dev: true + /has-flag/3.0.0: resolution: {integrity: sha1-tdRU3CGZriJWmfNGfloH87lVuv0=} engines: {node: '>=4'} @@ -3065,6 +3507,15 @@ packages: entities: 2.2.0 dev: true + /htmlparser2/7.1.2: + resolution: {integrity: sha512-d6cqsbJba2nRdg8WW2okyD4ceonFHn9jLFxhwlNcLhQWcFPdxXeJulgOLjLKtAK9T6ahd+GQNZwG9fjmGW7lyg==} + dependencies: + domelementtype: 2.2.0 + domhandler: 4.2.2 + domutils: 2.8.0 + entities: 3.0.1 + dev: true + /http-cache-semantics/4.1.0: resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} dev: true @@ -3244,6 +3695,15 @@ packages: through: 2.3.8 dev: true + /internal-slot/1.0.3: + resolution: {integrity: sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.1.1 + has: 1.0.3 + side-channel: 1.0.4 + dev: true + /internmap/2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} @@ -3258,6 +3718,32 @@ packages: resolution: {integrity: sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=} dev: true + /is-bigint/1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + dependencies: + has-bigints: 1.0.1 + dev: true + + /is-boolean-object/1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + has-tostringtag: 1.0.0 + dev: true + + /is-builtin-module/3.1.0: + resolution: {integrity: sha512-OV7JjAgOTfAFJmHZLvpSTb4qi0nIILDV1gWPYDnDJUTNFM5aGlRAhk4QcT8i7TuAleeEV5Fdkqn3t4mS+Q11fg==} + engines: {node: '>=6'} + dependencies: + builtin-modules: 3.2.0 + dev: true + + /is-callable/1.2.4: + resolution: {integrity: sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==} + engines: {node: '>= 0.4'} + dev: true + /is-ci/2.0.0: resolution: {integrity: sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==} hasBin: true @@ -3271,6 +3757,13 @@ packages: has: 1.0.3 dev: true + /is-date-object/1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-docker/2.2.1: resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} engines: {node: '>=8'} @@ -3338,11 +3831,23 @@ packages: resolution: {integrity: sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=} dev: true + /is-negative-zero/2.0.1: + resolution: {integrity: sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==} + engines: {node: '>= 0.4'} + dev: true + /is-npm/5.0.0: resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} engines: {node: '>=10'} dev: true + /is-number-object/1.0.6: + resolution: {integrity: sha512-bEVOqiRcvo3zO1+G2lVMy+gkkEm9Yh7cDMRusKKu5ZJKPUYSJwICTKZrNKHA2EbSP0Tu0+6B/emsYNHZyn6K8g==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + /is-number/7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -3404,6 +3909,10 @@ packages: scoped-regex: 2.1.0 dev: true + /is-shared-array-buffer/1.0.1: + resolution: {integrity: sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA==} + dev: true + /is-stream/1.1.0: resolution: {integrity: sha1-EtSj3U5o4Lec6428hBc66A2RykQ=} engines: {node: '>=0.10.0'} @@ -3414,6 +3923,20 @@ packages: engines: {node: '>=8'} dev: true + /is-string/1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + dependencies: + has-tostringtag: 1.0.0 + dev: true + + /is-symbol/1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + dependencies: + has-symbols: 1.0.2 + dev: true + /is-typedarray/1.0.0: resolution: {integrity: sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=} dev: true @@ -3428,6 +3951,12 @@ packages: engines: {node: '>=10'} dev: true + /is-weakref/1.0.1: + resolution: {integrity: sha512-b2jKc2pQZjaeFYWEf7ScFj+Be1I+PXmlu572Q8coTXZ+LD/QQZ7ShPMst8h16riVgyXTQwUsFEl74mDvc/3MHQ==} + dependencies: + call-bind: 1.0.2 + dev: true + /is-wsl/2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -3518,6 +4047,17 @@ packages: minimist: 1.2.5 dev: true + /jsonc-eslint-parser/1.4.1: + resolution: {integrity: sha512-hXBrvsR1rdjmB2kQmUjf1rEIa+TqHBGMge8pwi++C+Si1ad7EjZrJcpgwym+QGK/pqTx+K7keFAtLlVNdLRJOg==} + engines: {node: '>=8.10.0'} + dependencies: + acorn: 7.4.1 + eslint-utils: 2.1.0 + eslint-visitor-keys: 1.3.0 + espree: 6.2.1 + semver: 6.3.0 + dev: true + /jsonc-parser/2.3.1: resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} dev: true @@ -3539,6 +4079,14 @@ packages: promise: 7.3.1 dev: true + /jsx-ast-utils/3.2.1: + resolution: {integrity: sha512-uP5vu8xfy2F9A6LGC22KO7e2/vGTS1MhP+18f++ZNlf0Ohaxbc9nIEwHAsejlJKyzfZzU5UIhe5ItYkitcZnZA==} + engines: {node: '>=4.0'} + dependencies: + array-includes: 3.1.4 + object.assign: 4.1.2 + dev: true + /keyv/3.1.0: resolution: {integrity: sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==} dependencies: @@ -3654,6 +4202,14 @@ packages: mlly: 0.2.7 dev: true + /locate-path/2.0.0: + resolution: {integrity: sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=} + engines: {node: '>=4'} + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + dev: true + /locate-path/5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} engines: {node: '>=8'} @@ -3728,6 +4284,13 @@ packages: wrap-ansi: 3.0.1 dev: true + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: true + /lowercase-keys/1.0.1: resolution: {integrity: sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==} engines: {node: '>=0.10.0'} @@ -3868,10 +4431,18 @@ packages: import-meta-resolve: 1.1.1 dev: true + /ms/2.0.0: + resolution: {integrity: sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=} + dev: true + /ms/2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /multimap/1.1.0: + resolution: {integrity: sha512-0ZIR9PasPxGXmRsEF8jsDzndzHDj7tIav+JUmvIFB/WHswliFnquxECT/De7GR4yg99ky/NlRKJT82G1y271bw==} + dev: true + /mute-stream/0.0.7: resolution: {integrity: sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=} dev: true @@ -4025,6 +4596,59 @@ packages: engines: {node: '>=0.10.0'} dev: true + /object-inspect/1.11.0: + resolution: {integrity: sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg==} + dev: true + + /object-keys/1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + dev: true + + /object.assign/4.1.2: + resolution: {integrity: sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + has-symbols: 1.0.2 + object-keys: 1.1.1 + dev: true + + /object.entries/1.1.5: + resolution: {integrity: sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + + /object.fromentries/2.0.5: + resolution: {integrity: sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + + /object.hasown/1.1.0: + resolution: {integrity: sha512-MhjYRfj3GBlhSkDHo6QmvgjRLXQ2zndabdf3nX0yTyZK9rPfxb6uRpAac8HXNLy1GpqWtZ81Qh4v3uOls2sRAg==} + dependencies: + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + + /object.values/1.1.5: + resolution: {integrity: sha512-QUZRW0ilQ3PnPpbNtgdNV1PDbEqLIiSFB3l+EnGtBQ/8SUTLj1PZwtQHABZtLgwpJZTSZhuGLOGk57Drx2IvYg==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + dev: true + /once/1.4.0: resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=} dependencies: @@ -4114,6 +4738,13 @@ packages: engines: {node: '>=4'} dev: true + /p-limit/1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + dependencies: + p-try: 1.0.0 + dev: true + /p-limit/2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} engines: {node: '>=6'} @@ -4128,6 +4759,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-locate/2.0.0: + resolution: {integrity: sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=} + engines: {node: '>=4'} + dependencies: + p-limit: 1.3.0 + dev: true + /p-locate/4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -4189,6 +4827,11 @@ packages: engines: {node: '>=10'} dev: true + /p-try/1.0.0: + resolution: {integrity: sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=} + engines: {node: '>=4'} + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -4221,6 +4864,11 @@ packages: lines-and-columns: 1.1.6 dev: true + /path-exists/3.0.0: + resolution: {integrity: sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=} + engines: {node: '>=4'} + dev: true + /path-exists/4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4281,6 +4929,13 @@ packages: vue-demi: 0.11.4_vue@3.2.20 dev: false + /pkg-dir/2.0.0: + resolution: {integrity: sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=} + engines: {node: '>=4'} + dependencies: + find-up: 2.1.0 + dev: true + /pkg-dir/4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -4295,6 +4950,11 @@ packages: find-up: 5.0.0 dev: true + /pluralize/8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + dev: true + /pnpm/6.17.1: resolution: {integrity: sha512-rxl28iv4zYrhv7hKRMfO49hlXMFGQDgDyJrAoLkQPlJuAziCjEdfMV0OTt5BsntxbQMLsGgwpqSfosNVMbCdNQ==} engines: {node: '>=12.17'} @@ -4735,6 +5395,14 @@ packages: asap: 2.0.6 dev: true + /prop-types/15.7.2: + resolution: {integrity: sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: true + /pug-attrs/3.0.0: resolution: {integrity: sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA==} dependencies: @@ -4870,6 +5538,10 @@ packages: strip-json-comments: 2.0.1 dev: true + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: true + /read-pkg-up/7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -4897,6 +5569,19 @@ packages: strip-indent: 3.0.0 dev: true + /regexp-tree/0.1.24: + resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} + hasBin: true + dev: true + + /regexp.prototype.flags/1.3.1: + resolution: {integrity: sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + dev: true + /regexpp/3.2.0: resolution: {integrity: sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==} engines: {node: '>=8'} @@ -4949,6 +5634,13 @@ packages: path-parse: 1.0.7 dev: true + /resolve/2.0.0-next.3: + resolution: {integrity: sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==} + dependencies: + is-core-module: 2.8.0 + path-parse: 1.0.7 + dev: true + /responselike/1.0.2: resolution: {integrity: sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=} dependencies: @@ -5119,6 +5811,12 @@ packages: resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} dev: true + /safe-regex/2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + dependencies: + regexp-tree: 0.1.24 + dev: true + /safer-buffer/2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -5170,6 +5868,14 @@ packages: engines: {node: '>=8'} dev: true + /side-channel/1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.2 + get-intrinsic: 1.1.1 + object-inspect: 1.11.0 + dev: true + /signal-exit/3.0.5: resolution: {integrity: sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==} dev: true @@ -5290,6 +5996,33 @@ packages: strip-ansi: 6.0.1 dev: true + /string.prototype.matchall/4.0.6: + resolution: {integrity: sha512-6WgDX8HmQqvEd7J+G6VtAahhsQIssiZ8zl7zKh1VDMFyL3hRTJP4FTNA3RbIp2TOQ9AYNDcc7e3fH0Qbup+DBg==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + es-abstract: 1.19.1 + get-intrinsic: 1.1.1 + has-symbols: 1.0.2 + internal-slot: 1.0.3 + regexp.prototype.flags: 1.3.1 + side-channel: 1.0.4 + dev: true + + /string.prototype.trimend/1.0.4: + resolution: {integrity: sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + dev: true + + /string.prototype.trimstart/1.0.4: + resolution: {integrity: sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.1.3 + dev: true + /strip-ansi/3.0.1: resolution: {integrity: sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=} engines: {node: '>=0.10.0'} @@ -5318,6 +6051,11 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-bom/3.0.0: + resolution: {integrity: sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=} + engines: {node: '>=4'} + dev: true + /strip-final-newline/2.0.0: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} @@ -5490,6 +6228,15 @@ packages: engines: {node: '>=8'} dev: true + /tsconfig-paths/3.11.0: + resolution: {integrity: sha512-7ecdYDnIdmv639mmDwslG6KQg1Z9STTz1j7Gcz0xa+nshh/gKDAHcPxRbWOsA3SPp0tXP2leTcY9Kw+NAkfZzA==} + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.1 + minimist: 1.2.5 + strip-bom: 3.0.0 + dev: true + /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -5566,6 +6313,15 @@ packages: hasBin: true dev: true + /unbox-primitive/1.0.1: + resolution: {integrity: sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==} + dependencies: + function-bind: 1.1.1 + has-bigints: 1.0.1 + has-symbols: 1.0.2 + which-boxed-primitive: 1.0.2 + dev: true + /uniqs/2.0.0: resolution: {integrity: sha1-/+3ks2slKQaW5uFl1KWe25mOawI=} dev: true @@ -5910,6 +6666,16 @@ packages: resolution: {integrity: sha512-5NUqC2JquIL2pBAAo/VfBP6KuGkHIZQXW/lNKupLPfhViwh8wNsu0BObtl09yuKZszeEUfbXz8xhrHvSG16Nqw==} dev: true + /which-boxed-primitive/1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.6 + is-string: 1.0.7 + is-symbol: 1.0.4 + dev: true + /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -5979,6 +6745,14 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml-eslint-parser/0.4.1: + resolution: {integrity: sha512-GoJ/p1EW8O2tbTbuhfxjo1XhfUFU3uX3kwvfEQoOaZjO2Lubx8POjlsSqB+18b3SxkujAdQYT9r9nURaUWNYWQ==} + dependencies: + eslint-visitor-keys: 2.1.0 + lodash: 4.17.21 + yaml: 1.10.2 + dev: true + /yaml/1.10.2: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index 72cd02c0..fa9e1028 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -16,6 +16,7 @@ declare global { const debouncedWatch: typeof import('@vueuse/core')['debouncedWatch'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] const defineComponent: typeof import('vue')['defineComponent'] + const eagerComputed: typeof import('@vueuse/core')['eagerComputed'] const extendRef: typeof import('@vueuse/core')['extendRef'] const getCurrentInstance: typeof import('vue')['getCurrentInstance'] const h: typeof import('vue')['h'] @@ -60,6 +61,7 @@ declare global { const toRef: typeof import('vue')['toRef'] const toRefs: typeof import('vue')['toRefs'] const triggerRef: typeof import('vue')['triggerRef'] + const tryOnBeforeUnmount: typeof import('@vueuse/core')['tryOnBeforeUnmount'] const tryOnMounted: typeof import('@vueuse/core')['tryOnMounted'] const tryOnScopeDispose: typeof import('@vueuse/core')['tryOnScopeDispose'] const tryOnUnmounted: typeof import('@vueuse/core')['tryOnUnmounted'] @@ -78,12 +80,14 @@ declare global { const useCssVar: typeof import('@vueuse/core')['useCssVar'] const useDark: typeof import('@vueuse/core')['useDark'] const useDebounce: typeof import('@vueuse/core')['useDebounce'] + const useDebouncedRefHistory: typeof import('@vueuse/core')['useDebouncedRefHistory'] const useDebounceFn: typeof import('@vueuse/core')['useDebounceFn'] const useDeviceMotion: typeof import('@vueuse/core')['useDeviceMotion'] const useDeviceOrientation: typeof import('@vueuse/core')['useDeviceOrientation'] const useDevicePixelRatio: typeof import('@vueuse/core')['useDevicePixelRatio'] const useDevicesList: typeof import('@vueuse/core')['useDevicesList'] const useDocumentVisibility: typeof import('@vueuse/core')['useDocumentVisibility'] + const useDraggable: typeof import('@vueuse/core')['useDraggable'] const useElementBounding: typeof import('@vueuse/core')['useElementBounding'] const useElementSize: typeof import('@vueuse/core')['useElementSize'] const useElementVisibility: typeof import('@vueuse/core')['useElementVisibility'] @@ -98,6 +102,7 @@ declare global { const useIntersectionObserver: typeof import('@vueuse/core')['useIntersectionObserver'] const useInterval: typeof import('@vueuse/core')['useInterval'] const useIntervalFn: typeof import('@vueuse/core')['useIntervalFn'] + const useKeyModifier: typeof import('@vueuse/core')['useKeyModifier'] const useLastChanged: typeof import('@vueuse/core')['useLastChanged'] const useLocalStorage: typeof import('@vueuse/core')['useLocalStorage'] const useMagicKeys: typeof import('@vueuse/core')['useMagicKeys'] @@ -114,6 +119,7 @@ declare global { const usePageLeave: typeof import('@vueuse/core')['usePageLeave'] const useParallax: typeof import('@vueuse/core')['useParallax'] const usePermission: typeof import('@vueuse/core')['usePermission'] + const usePointer: typeof import('@vueuse/core')['usePointer'] const usePointerSwipe: typeof import('@vueuse/core')['usePointerSwipe'] const usePreferredColorScheme: typeof import('@vueuse/core')['usePreferredColorScheme'] const usePreferredDark: typeof import('@vueuse/core')['usePreferredDark'] @@ -122,6 +128,7 @@ declare global { const useRefHistory: typeof import('@vueuse/core')['useRefHistory'] const useResizeObserver: typeof import('@vueuse/core')['useResizeObserver'] const useScriptTag: typeof import('@vueuse/core')['useScriptTag'] + const useScroll: typeof import('@vueuse/core')['useScroll'] const useSessionStorage: typeof import('@vueuse/core')['useSessionStorage'] const useShare: typeof import('@vueuse/core')['useShare'] const useSlots: typeof import('vue')['useSlots'] @@ -130,6 +137,7 @@ declare global { const useSwipe: typeof import('@vueuse/core')['useSwipe'] const useTemplateRefsList: typeof import('@vueuse/core')['useTemplateRefsList'] const useThrottle: typeof import('@vueuse/core')['useThrottle'] + const useThrottledRefHistory: typeof import('@vueuse/core')['useThrottledRefHistory'] const useThrottleFn: typeof import('@vueuse/core')['useThrottleFn'] const useTimeAgo: typeof import('@vueuse/core')['useTimeAgo'] const useTimeout: typeof import('@vueuse/core')['useTimeout'] @@ -140,8 +148,10 @@ declare global { const useTransition: typeof import('@vueuse/core')['useTransition'] const useUrlSearchParams: typeof import('@vueuse/core')['useUrlSearchParams'] const useUserMedia: typeof import('@vueuse/core')['useUserMedia'] + const useVirtualList: typeof import('@vueuse/core')['useVirtualList'] const useVModel: typeof import('@vueuse/core')['useVModel'] const useVModels: typeof import('@vueuse/core')['useVModels'] + const useWakeLock: typeof import('@vueuse/core')['useWakeLock'] const useWebSocket: typeof import('@vueuse/core')['useWebSocket'] const useWebWorker: typeof import('@vueuse/core')['useWebWorker'] const useWebWorkerFn: typeof import('@vueuse/core')['useWebWorkerFn'] @@ -149,7 +159,9 @@ declare global { const useWindowScroll: typeof import('@vueuse/core')['useWindowScroll'] const useWindowSize: typeof import('@vueuse/core')['useWindowSize'] const watch: typeof import('vue')['watch'] + const watchAtMost: typeof import('@vueuse/core')['watchAtMost'] const watchEffect: typeof import('vue')['watchEffect'] + const watchOnce: typeof import('@vueuse/core')['watchOnce'] const watchWithFilter: typeof import('@vueuse/core')['watchWithFilter'] const whenever: typeof import('@vueuse/core')['whenever'] } diff --git a/src/composables/useZoom.ts b/src/composables/useZoom.ts new file mode 100644 index 00000000..fbac3b5d --- /dev/null +++ b/src/composables/useZoom.ts @@ -0,0 +1,187 @@ +import { D3ZoomEvent, zoom, zoomIdentity, ZoomTransform } from 'd3-zoom' +import { pointer, select } from 'd3-selection' +import { Ref } from 'vue' +import { FlowTransform, PanOnScrollMode, RevueFlowOptions } from '~/types' +import { RevueFlowHooks } from '~/hooks/RevueFlowHooks' +import { clamp } from '~/utils' +import useKeyPress from '~/hooks/useKeyPress' +import useZoomPanHelper from '~/hooks/useZoomPanHelper' +import { onLoadGetElements, onLoadProject, onLoadToObject } from '~/utils/graph' + +const viewChanged = (prevTransform: FlowTransform, eventTransform: ZoomTransform): boolean => + prevTransform.x !== eventTransform.x || prevTransform.y !== eventTransform.y || prevTransform.zoom !== eventTransform.k + +const eventToFlowTransform = (eventTransform: ZoomTransform): FlowTransform => ({ + x: eventTransform.x, + y: eventTransform.y, + zoom: eventTransform.k, +}) + +type UseZoomOptions = RevueFlowOptions + +const useZoom = (zoomPane: Ref, options: UseZoomOptions) => { + const { + selectionKeyCode = 'Shift', + zoomActivationKeyCode = 'Meta', + minZoom = 0.5, + maxZoom = 2, + defaultZoom = 1, + defaultPosition = [0, 0], + translateExtent = [ + [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], + [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], + ], + zoomOnScroll = true, + zoomOnPinch = true, + zoomOnDoubleClick = true, + panOnScroll = false, + panOnScrollSpeed = 0.5, + panOnScrollMode = PanOnScrollMode.Free, + paneMoveable = true, + } = options + const hooks = inject('hooks')! + const prevTransform = ref({ x: 0, y: 0, zoom: 0 }) + + const clampedX = clamp(defaultPosition[0], translateExtent[0][0], translateExtent[1][0]) + const clampedY = clamp(defaultPosition[1], translateExtent[0][1], translateExtent[1][1]) + const clampedZoom = clamp(defaultZoom, minZoom, maxZoom) + const transform = ref([clampedX, clampedY, clampedZoom]) + + until(zoomPane) + .toBeTruthy() + .then(() => { + const d3Zoom = zoom().scaleExtent([minZoom, maxZoom]).translateExtent(translateExtent) + const d3Selection = select(unrefElement(zoomPane) as Element).call(d3Zoom) + const d3ZoomHandler = d3Selection.on('wheel.zoom') + + const updatedTransform = zoomIdentity.translate(clampedX, clampedY).scale(clampedZoom) + + const { fitView, zoomIn, zoomOut, zoomTo } = useZoomPanHelper({ ...options, d3Zoom, d3Selection } as any) + hooks.load.trigger({ + fitView: (params = { padding: 0.1 }) => fitView(params), + zoomIn, + zoomOut, + zoomTo, + setTransform: transform, + } as any) + d3Zoom.transform(d3Selection, updatedTransform) + + const applyZoomHandlers = () => { + d3Zoom.on('start', (event: D3ZoomEvent) => { + if (viewChanged(prevTransform.value, event.transform)) { + const flowTransform = eventToFlowTransform(event.transform) + prevTransform.value = flowTransform + + hooks.moveStart.trigger(flowTransform) + } + }) + + d3Zoom.on('end', (event: D3ZoomEvent) => { + if (viewChanged(prevTransform.value, event.transform)) { + const flowTransform = eventToFlowTransform(event.transform) + prevTransform.value = flowTransform + + hooks.moveEnd.trigger(flowTransform) + } + }) + + useKeyPress(selectionKeyCode, (keyPress) => { + if (keyPress) { + d3Zoom.on('zoom', null) + } else { + d3Zoom.on('zoom', (event: D3ZoomEvent) => { + transform.value = [event.transform.x, event.transform.y, event.transform.k] + hooks.move.trigger(eventToFlowTransform(event.transform)) + }) + } + }) + + useKeyPress(zoomActivationKeyCode, (keyPress) => { + if (panOnScroll && keyPress) { + d3Selection + ?.on('wheel', (event: WheelEvent) => { + event.preventDefault() + event.stopImmediatePropagation() + + const currentZoom = d3Selection?.property('__zoom').k || 1 + + if (event.ctrlKey && zoomOnPinch) { + const point = pointer(event) + // taken from https://github.com/d3/d3-zoom/blob/master/src/zoom.js + const pinchDelta = -event.deltaY * (event.deltaMode === 1 ? 0.05 : event.deltaMode ? 1 : 0.002) * 10 + const zoom = currentZoom * Math.pow(2, pinchDelta) + if (d3Selection) d3Zoom?.scaleTo(d3Selection, zoom, point) + + return + } + + // increase scroll speed in firefox + // firefox: deltaMode === 1; chrome: deltaMode === 0 + const deltaNormalize = event.deltaMode === 1 ? 20 : 1 + const deltaX = panOnScrollMode === PanOnScrollMode.Vertical ? 0 : event.deltaX * deltaNormalize + const deltaY = panOnScrollMode === PanOnScrollMode.Horizontal ? 0 : event.deltaY * deltaNormalize + + if (d3Selection && panOnScrollSpeed) + d3Zoom?.translateBy( + d3Selection, + -(deltaX / currentZoom) * panOnScrollSpeed, + -(deltaY / currentZoom) * panOnScrollSpeed, + ) + }) + .on('wheel.zoom', null) + } else if (typeof d3ZoomHandler !== 'undefined') { + d3Selection?.on('wheel', null).on('wheel.zoom', d3ZoomHandler) + } + }) + } + + const applyZoomFilter = () => { + const keyPress = useKeyPress(selectionKeyCode) + d3Zoom.filter((event: MouseEvent) => { + const zoomScroll = zoomOnScroll + const pinchZoom = zoomOnPinch && event.ctrlKey + + // if all interactions are disabled, we prevent all zoom events + if (!paneMoveable && !zoomScroll && !panOnScroll && !zoomOnDoubleClick && !zoomOnPinch) return false + + // during a selection we prevent all other interactions + if (keyPress.value) return false + + // if zoom on double click is disabled, we prevent the double click event + if (!zoomOnDoubleClick && event.type === 'dblclick') return false + + if ((event.target as Element).closest('.nowheel') && event.type === 'wheel') return false + + // when the target element is a node, we still allow zooming + if ( + ((event.target as Element).closest('.revue-flow__node') || (event.target as Element).closest('.revue-flow__edge')) && + event.type !== 'wheel' + ) + return false + + // when the target element is a node selection, we still allow zooming + if ((event.target as Element).closest('.revue-flow__nodesselection') && event.type !== 'wheel') return false + + if (!zoomOnPinch && event.ctrlKey && event.type === 'wheel') return false + + // when there is no scroll handling enabled, we prevent all wheel events + if (!zoomScroll && !panOnScroll && !pinchZoom && event.type === 'wheel') return false + + // if the pane is not movable, we prevent dragging it with mousestart or touchstart + if (!paneMoveable && (event.type === 'mousedown' || event.type === 'touchstart')) return false + + // default filter for d3-zoom + return (!event.ctrlKey || event.type === 'wheel') && !event.button + }) + } + + applyZoomHandlers() + applyZoomFilter() + }) + + return { + transform, + } +} + +export default useZoom diff --git a/src/container/EdgeRenderer.vue b/src/container/EdgeRenderer.vue new file mode 100644 index 00000000..d384f7ec --- /dev/null +++ b/src/container/EdgeRenderer.vue @@ -0,0 +1,49 @@ + + diff --git a/src/container/EdgeRenderer/utils.ts b/src/container/EdgeRenderer/utils.ts index 103b1f95..e1d193e5 100644 --- a/src/container/EdgeRenderer/utils.ts +++ b/src/container/EdgeRenderer/utils.ts @@ -1,13 +1,13 @@ -import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '../../components/Edges' -import { rectToBox } from '../../utils/graph' -import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '../../types' +import { BezierEdge, StepEdge, SmoothStepEdge, StraightEdge } from '~/components/Edges' +import { rectToBox } from '~/utils/graph' +import { Position, Node, XYPosition, ElementId, HandleElement, Transform, Edge, EdgeType } from '~/types' export function createEdgeTypes(edgeTypes: Record): Record { const standardTypes: Record = { default: edgeTypes.default || BezierEdge, straight: edgeTypes.bezier || StraightEdge, step: edgeTypes.step || StepEdge, - smoothstep: edgeTypes.step || SmoothStepEdge + smoothstep: edgeTypes.step || SmoothStepEdge, } const wrappedTypes = {} as Record @@ -21,7 +21,7 @@ export function createEdgeTypes(edgeTypes: Record): Record { const sourceHandlePos = getHandlePosition(sourcePosition, sourceNode, sourceHandle) const targetHandlePos = getHandlePosition(targetPosition, targetNode, targetHandle) @@ -94,7 +94,7 @@ export const getEdgePositions = ( sourceX: sourceHandlePos.x, sourceY: sourceHandlePos.y, targetX: targetHandlePos.x, - targetY: targetHandlePos.y + targetY: targetHandlePos.y, } } @@ -111,7 +111,7 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform } x: Math.min(sourcePos.x, targetPos.x), y: Math.min(sourcePos.y, targetPos.y), x2: Math.max(sourcePos.x, targetPos.x), - y2: Math.max(sourcePos.y, targetPos.y) + y2: Math.max(sourcePos.y, targetPos.y), } if (edgeBox.x === edgeBox.x2) { @@ -126,7 +126,7 @@ export function isEdgeVisible({ sourcePos, targetPos, width, height, transform } x: (0 - transform[0]) / transform[2], y: (0 - transform[1]) / transform[2], width: width / transform[2], - height: height / transform[2] + height: height / transform[2], }) const xOverlap = Math.max(0, Math.min(viewBox.x2, edgeBox.x2) - Math.max(viewBox.x, edgeBox.x)) @@ -152,6 +152,6 @@ export const getSourceTargetNodes = (edge: Edge, nodes: Node[]): SourceTargetNod } return res }, - { sourceNode: null, targetNode: null } as SourceTargetNode + { sourceNode: null, targetNode: null } as SourceTargetNode, ) } diff --git a/src/container/Flow.vue b/src/container/Flow.vue new file mode 100644 index 00000000..f3267379 --- /dev/null +++ b/src/container/Flow.vue @@ -0,0 +1,215 @@ + + diff --git a/src/container/Marker.vue b/src/container/Marker.vue new file mode 100644 index 00000000..b461f1f2 --- /dev/null +++ b/src/container/Marker.vue @@ -0,0 +1,21 @@ + + diff --git a/src/container/MarkerDefinitions.vue b/src/container/MarkerDefinitions.vue new file mode 100644 index 00000000..2cbd2938 --- /dev/null +++ b/src/container/MarkerDefinitions.vue @@ -0,0 +1,35 @@ + + diff --git a/src/container/NodeRenderer.vue b/src/container/NodeRenderer.vue new file mode 100644 index 00000000..cdfe31b3 --- /dev/null +++ b/src/container/NodeRenderer.vue @@ -0,0 +1,74 @@ + + diff --git a/src/container/NodeRenderer/utils.ts b/src/container/NodeRenderer/utils.ts index 963258d0..fbe63b5f 100644 --- a/src/container/NodeRenderer/utils.ts +++ b/src/container/NodeRenderer/utils.ts @@ -1,13 +1,13 @@ -import DefaultNode from '../../components/Nodes/DefaultNode' -import InputNode from '../../components/Nodes/InputNode' -import OutputNode from '../../components/Nodes/OutputNode' -import { NodeType } from '../../types' +import DefaultNode from '~/components/Nodes/DefaultNode' +import InputNode from '~/components/Nodes/InputNode' +import OutputNode from '~/components/Nodes/OutputNode' +import { NodeType } from '~/types' export function createNodeTypes(nodeTypes: Record): Record { const standardTypes: Record = { input: nodeTypes.input || InputNode, default: nodeTypes.default || DefaultNode, - output: nodeTypes.output || OutputNode + output: nodeTypes.output || OutputNode, } const wrappedTypes = {} as NodeType @@ -21,6 +21,6 @@ export function createNodeTypes(nodeTypes: Record): Record +import { inject } from 'vue' +import { RevueFlowHooks } from '~/hooks/RevueFlowHooks' +import useKeyPress from '~/hooks/useKeyPress' +import { KeyCode } from '~/types' +import useGlobalKeyHandler from '~/hooks/useGlobalKeyHandler' + +const props = withDefaults( + defineProps<{ + selectionKeyCode: KeyCode + deleteKeyCode: KeyCode + multiSelectionKeyCode: KeyCode + }>(), + { + selectionKeyCode: 'Shift', + deleteKeyCode: 'Backspace', + multiSelectionKeyCode: 'Meta', + }, +) +const hooks = inject('hooks')! +const keyPressed = useKeyPress(props.selectionKeyCode) +const nodeSelectionActive = ref(false) +const selectedElements = ref([]) + +const onClick = (event: MouseEvent) => { + hooks.paneClick.trigger(event) + nodeSelectionActive.value = false + selectedElements.value = [] +} + +const onContextMenu = (event: MouseEvent) => hooks.paneContextMenu.trigger(event) + +const onWheel = (event: WheelEvent) => hooks.paneScroll.trigger(event) + +useGlobalKeyHandler({ + onElementsRemove: hooks.elementsRemove.trigger, + deleteKeyCode: props.deleteKeyCode, + multiSelectionKeyCode: props.multiSelectionKeyCode, +}) + + diff --git a/src/container/ZoomPane.vue b/src/container/ZoomPane.vue new file mode 100644 index 00000000..d6be55af --- /dev/null +++ b/src/container/ZoomPane.vue @@ -0,0 +1,54 @@ + + diff --git a/src/hooks/RevueFlowHooks.ts b/src/hooks/RevueFlowHooks.ts index 543ae9eb..522e5dfd 100644 --- a/src/hooks/RevueFlowHooks.ts +++ b/src/hooks/RevueFlowHooks.ts @@ -1,6 +1,6 @@ // global hooks +import { EventHook } from '@vueuse/core' import { Connection, Edge, Elements, FlowTransform, Node, OnConnectStartParams, OnLoadParams } from '../types' -import { createEventHook, EventHook } from '@vueuse/core' export interface RevueFlowHooks { elementClick: EventHook<{ event: MouseEvent; element: Node | Edge }> @@ -84,7 +84,7 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, edgeDoubleClick: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeClick: createEventHook<{ event: MouseEvent; edge: Edge }>(), edgeUpdateStart: createEventHook<{ event: MouseEvent; edge: Edge }>(), - edgeUpdateEnd: createEventHook<{ event: MouseEvent; edge: Edge }>() + edgeUpdateEnd: createEventHook<{ event: MouseEvent; edge: Edge }>(), } const bind = (emit: (event: string, args: any) => void) => { @@ -99,6 +99,6 @@ export const useRevueFlow = (): RevueFlowHooks & { bind: (emit: (event: string, return { ...eventHooks, - bind + bind, } } diff --git a/src/hooks/useGlobalKeyHandler.ts b/src/hooks/useGlobalKeyHandler.ts index dd66055d..09e1b70c 100644 --- a/src/hooks/useGlobalKeyHandler.ts +++ b/src/hooks/useGlobalKeyHandler.ts @@ -1,28 +1,29 @@ -import useKeyPress from './useKeyPress' -import { isNode, getConnectedEdges } from '../utils/graph' -import { Elements, KeyCode, ElementId, FlowElement, RevueFlowStore, Edge } from '../types' import { computed, watch } from 'vue' +import useKeyPress from './useKeyPress' +import { isNode, getConnectedEdges } from '~/utils/graph' +import { Elements, KeyCode, ElementId, FlowElement, RevueFlowStore, Edge } from '~/types' interface HookParams { - store: RevueFlowStore deleteKeyCode: KeyCode multiSelectionKeyCode: KeyCode onElementsRemove?: (elements: Elements) => void } -export default ({ store, deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => { +export default ({ deleteKeyCode, multiSelectionKeyCode, onElementsRemove }: HookParams): void => { + const selectedElements = ref([]) const deleteKeyPressed = useKeyPress(deleteKeyCode) const multiSelectionKeyPressed = useKeyPress(multiSelectionKeyCode) - const selectedElements = computed(() => store.selectedElements || []) - const edges = computed(() => store.edges) + /** + * const selectedElements = computed(() => store.selectedElements || []) + const edges = computed(() => store.edges) - watch(selectedElements, () => { + watch(selectedElements, () => { if (onElementsRemove && deleteKeyPressed.value && selectedElements.value.length > 0) { const selectedNodes = selectedElements.value.filter(isNode) const connectedEdges = getConnectedEdges(selectedNodes, edges.value as Edge[]) const elementsToRemove = [...selectedElements.value, ...connectedEdges].reduce( (res, item) => res.set(item.id, item), - new Map() + new Map(), ) onElementsRemove(Array.from(elementsToRemove.values())) @@ -31,7 +32,8 @@ export default ({ store, deleteKeyCode, multiSelectionKeyCode, onElementsRemove } }) - watch(multiSelectionKeyPressed, () => { + watch(multiSelectionKeyPressed, () => { store.multiSelectionActive = multiSelectionKeyPressed.value }) + */ } diff --git a/src/hooks/useKeyPress.ts b/src/hooks/useKeyPress.ts index 6c621950..16c697b5 100644 --- a/src/hooks/useKeyPress.ts +++ b/src/hooks/useKeyPress.ts @@ -2,13 +2,13 @@ import { Ref } from 'vue' import { onKeyDown, onKeyPressed, onKeyUp } from '@vueuse/core' import { KeyCode } from '~/types' -export default (keyCode: KeyCode, cb?: (keyPress: boolean) => void): Ref => { - const keyPressed = controlledRef(false, { +export default (keyCode: KeyCode, onChange?: (keyPressed: boolean) => void): Ref => { + const isPressed = controlledRef(false, { onBeforeChange(val, oldVal) { if (val === oldVal) return false }, onChanged() { - if (cb && typeof cb === 'function') cb(keyPressed.value) + if (onChange && typeof onChange === 'function') onChange(isPressed.value) }, }) @@ -16,7 +16,7 @@ export default (keyCode: KeyCode, cb?: (keyPress: boolean) => void): Ref e.key === keyCode || e.keyCode === keyCode, (e) => { e.preventDefault() - keyPressed.value = true + isPressed.value = true }, ) @@ -24,7 +24,7 @@ export default (keyCode: KeyCode, cb?: (keyPress: boolean) => void): Ref e.key === keyCode || e.keyCode === keyCode, (e) => { e.preventDefault() - keyPressed.value = true + isPressed.value = true }, ) @@ -32,15 +32,15 @@ export default (keyCode: KeyCode, cb?: (keyPress: boolean) => void): Ref event.key === keyCode || event.keyCode === keyCode, (e) => { e.preventDefault() - keyPressed.value = false + isPressed.value = false }, ) useEventListener(window, 'blur', () => { - keyPressed.value = false + isPressed.value = false }) - if (cb && typeof cb === 'function') cb(keyPressed.value) + if (onChange && typeof onChange === 'function') onChange(isPressed.value) - return keyPressed + return isPressed } diff --git a/src/hooks/useResizeHandler.ts b/src/hooks/useResizeHandler.ts index 2f56ae29..7b4f20f0 100644 --- a/src/hooks/useResizeHandler.ts +++ b/src/hooks/useResizeHandler.ts @@ -1,33 +1,20 @@ -import { createEventHook, MaybeRef, unrefElement, until, useEventListener, useResizeObserver } from '@vueuse/core' -import { getDimensions } from '../utils' -import { Dimensions } from '../types' +import { Ref } from 'vue' +import { getDimensions } from '~/utils' +import { Dimensions } from '~/types' -export default (rendererNode: MaybeRef, updateSize: (size: Dimensions) => any) => { - const resizeHandlerHook = createEventHook() +export default (el: Ref) => { + const dimensions = ref({ width: 0, height: 0 }) const updateDimensions = () => { - if (!unrefElement(rendererNode)) { - return - } + const unrefEl = unrefElement(el) + if (!unrefEl) return - const size = getDimensions(unrefElement(rendererNode)) - - if (size.height === 0 || size.width === 0) { + const size = getDimensions(unrefEl as HTMLDivElement) + if (size.height === 0 || size.width === 0) console.log('The revue Flow parent container needs a width and a height to render the graph.') - } - - updateSize(size) } - until(rendererNode) - .toBeTruthy() - .then(() => { - updateDimensions() - useEventListener(window, 'resize', updateDimensions) - useResizeObserver(unrefElement(rendererNode), () => updateDimensions()) - resizeHandlerHook.trigger(true) - }) + useEventListener(window, 'resize', updateDimensions) + useResizeObserver(el, () => updateDimensions()) - return { - onReady: resizeHandlerHook.on - } + return dimensions } diff --git a/src/hooks/useZoomPanHelper.ts b/src/hooks/useZoomPanHelper.ts index 47502de1..f245b38b 100644 --- a/src/hooks/useZoomPanHelper.ts +++ b/src/hooks/useZoomPanHelper.ts @@ -1,91 +1,76 @@ import { zoomIdentity } from 'd3-zoom' -import { computed, effectScope, inject } from 'vue' -import { createEventHook, EventHookOn, until } from '@vueuse/core' import { Selection, ZoomBehavior } from 'd3' -import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '../utils/graph' -import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition, RevueFlowStore } from '../types' +import { getRectOfNodes, pointToRendererPoint, getTransformForBounds } from '~/utils/graph' +import { FitViewParams, FlowTransform, ZoomPanHelperFunctions, Rect, XYPosition, Node, Transform, SnapGrid } from '~/types' const DEFAULT_PADDING = 0.1 -const useZoomPanHelper = (): { onReady: EventHookOn } => { - const zoomPanHelperEvent = createEventHook() - const store = inject('store')! +const useZoomPanHelper = ({ + d3Zoom, + d3Selection, + nodes = [], + width = 0, + height = 0, + minZoom = 0.5, + maxZoom = 2, + transform = [0, 0, 0], + snapToGrid = false, + snapGrid = [15, 15], +}: { + d3Zoom: ZoomBehavior + d3Selection: Selection + nodes: Node[] + width: number + height: number + minZoom: number + maxZoom: number + transform: Transform + snapToGrid: boolean + snapGrid: SnapGrid +}): ZoomPanHelperFunctions => ({ + zoomIn: () => d3Zoom?.scaleBy(d3Selection, 1.2), + zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2), + zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel), + transform: (transform: FlowTransform) => { + const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom) - const scope = effectScope() - scope.run(() => { - const d3Zoom = computed(() => store.d3Zoom) - const d3Selection = computed(() => store.d3Selection) + d3Zoom.transform(d3Selection, nextTransform) + }, + fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { + if (!nodes.length) { + return + } - const createZoomPanHelper = ( - d3Zoom: ZoomBehavior, - d3Selection: Selection - ): ZoomPanHelperFunctions => ({ - zoomIn: () => d3Zoom?.scaleBy(d3Selection, 1.2), - zoomOut: () => d3Zoom?.scaleBy(d3Selection, 1 / 1.2), - zoomTo: (zoomLevel: number) => d3Zoom.scaleTo(d3Selection, zoomLevel), - transform: (transform: FlowTransform) => { - const nextTransform = zoomIdentity.translate(transform.x, transform.y).scale(transform.zoom) + const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden)) + const [x, y, zoom] = getTransformForBounds( + bounds, + width, + height, + options.minZoom || minZoom, + options.maxZoom || maxZoom, + options.padding || DEFAULT_PADDING, + ) + const transform = zoomIdentity.translate(x, y).scale(zoom) - d3Zoom.transform(d3Selection, nextTransform) - }, - fitView: (options: FitViewParams = { padding: DEFAULT_PADDING, includeHiddenNodes: false }) => { - const { nodes, width, height, minZoom, maxZoom } = store + d3Zoom?.transform(d3Selection, transform) + }, + setCenter: (x: number, y: number, zoom?: number) => { + const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom + const centerX = width / 2 - x * nextZoom + const centerY = height / 2 - y * nextZoom + const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom) - if (!nodes.length) { - return - } + d3Zoom.transform(d3Selection, transform) + }, + fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { + const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding) + const transform = zoomIdentity.translate(x, y).scale(zoom) - const bounds = getRectOfNodes(options.includeHiddenNodes ? nodes : nodes.filter((node) => !node.isHidden)) - const [x, y, zoom] = getTransformForBounds( - bounds, - width, - height, - options.minZoom || minZoom, - options.maxZoom || maxZoom, - options.padding || DEFAULT_PADDING - ) - const transform = zoomIdentity.translate(x, y).scale(zoom) - - d3Zoom?.transform(d3Selection, transform) - }, - setCenter: (x: number, y: number, zoom?: number) => { - const { width, height, maxZoom } = store - - const nextZoom = typeof zoom !== 'undefined' ? zoom : maxZoom - const centerX = width / 2 - x * nextZoom - const centerY = height / 2 - y * nextZoom - const transform = zoomIdentity.translate(centerX, centerY).scale(nextZoom) - - d3Zoom.transform(d3Selection, transform) - }, - fitBounds: (bounds: Rect, padding = DEFAULT_PADDING) => { - const { width, height, minZoom, maxZoom } = store - const [x, y, zoom] = getTransformForBounds(bounds, width, height, minZoom, maxZoom, padding) - const transform = zoomIdentity.translate(x, y).scale(zoom) - - d3Zoom.transform(d3Selection, transform) - }, - project: (position: XYPosition) => { - const { transform, snapToGrid, snapGrid } = store - return pointToRendererPoint(position, transform, snapToGrid, snapGrid) - } - }) - - until(d3Zoom) - .toBeTruthy() - .then(() => { - if (d3Zoom.value && d3Selection.value) { - zoomPanHelperEvent.trigger(createZoomPanHelper(d3Zoom.value, d3Selection.value)) - } else { - throw Error('D3 could not be initialized.') - } - scope.stop() - }) - }) - - return { - onReady: zoomPanHelperEvent.on - } -} + d3Zoom.transform(d3Selection, transform) + }, + project: (position: XYPosition) => { + return pointToRendererPoint(position, transform, snapToGrid, snapGrid) + }, +}) export default useZoomPanHelper diff --git a/src/store/configure-store.ts b/src/store/configure-store.ts index aa9725e0..6f6182be 100644 --- a/src/store/configure-store.ts +++ b/src/store/configure-store.ts @@ -1,9 +1,9 @@ import { setActivePinia, createPinia, defineStore, StoreDefinition } from 'pinia' import isEqual from 'fast-deep-equal' -import { Edge, Node, NodeDiffUpdate, RevueFlowState, RevueFlowActions, XYPosition } from '../types' -import { getConnectedEdges, getNodesInside, getRectOfNodes, isEdge, isNode, parseEdge, parseNode } from '../utils/graph' -import { clampPosition, getDimensions } from '../utils' -import { getHandleBounds } from '../components/Nodes/utils' +import { Edge, Node, NodeDiffUpdate, RevueFlowState, RevueFlowActions, XYPosition } from '~/types' +import { clampPosition, getDimensions } from '~/utils' +import { getConnectedEdges, getNodesInside, getRectOfNodes, isEdge, isNode, parseEdge, parseNode } from '~/utils/graph' +import { getHandleBounds } from '~/components/Nodes/utils' type NextElements = { nextNodes: Node[] @@ -12,14 +12,14 @@ type NextElements = { const pinia = createPinia() export default function configureStore( - preloadedState: RevueFlowState + preloadedState: RevueFlowState, ): StoreDefinition { setActivePinia(pinia) return defineStore({ - id: 'revue-flow-' + Math.random(), + id: `revue-flow-${Math.random()}`, state: () => ({ - ...preloadedState + ...preloadedState, }), getters: {}, actions: { @@ -27,7 +27,7 @@ export default function configureStore( const propElements = elements const nextElements: NextElements = { nextNodes: [], - nextEdges: [] + nextEdges: [], } const { nextNodes, nextEdges } = propElements.reduce((res, propElement): NextElements => { if (isNode(propElement)) { @@ -36,7 +36,7 @@ export default function configureStore( if (storeNode) { const updatedNode: Node = { ...storeNode, - ...propElement + ...propElement, } if (storeNode.position.x !== propElement.position.x || storeNode.position.y !== propElement.position.y) { @@ -59,7 +59,7 @@ export default function configureStore( if (storeEdge) { res.nextEdges.push({ ...storeEdge, - ...propElement + ...propElement, }) } else { res.nextEdges.push(parseEdge(propElement)) @@ -90,8 +90,8 @@ export default function configureStore( __rf: { ...node.__rf, ...dimensions, - handleBounds - } + handleBounds, + }, } } } @@ -107,7 +107,7 @@ export default function configureStore( const [gridSizeX, gridSizeY] = this.snapGrid position = { x: gridSizeX * Math.round(pos.x / gridSizeX), - y: gridSizeY * Math.round(pos.y / gridSizeY) + y: gridSizeY * Math.round(pos.y / gridSizeY), } } @@ -117,8 +117,8 @@ export default function configureStore( ...node, __rf: { ...node.__rf, - position - } + position, + }, } } @@ -134,14 +134,14 @@ export default function configureStore( ...node, __rf: { ...node.__rf, - isDragging - } + isDragging, + }, } if (diff) { updatedNode.__rf.position = { x: node.__rf.position.x + diff.x, - y: node.__rf.position.y + diff.y + y: node.__rf.position.y + diff.y, } } @@ -160,7 +160,7 @@ export default function configureStore( startY: mousePos.y, x: mousePos.x, y: mousePos.y, - draw: true + draw: true, } }, updateUserSelection(mousePos) { @@ -172,7 +172,7 @@ export default function configureStore( x: mousePos.x < startX ? mousePos.x : this.userSelectionRect.x, y: mousePos.y < startY ? mousePos.y : this.userSelectionRect.y, width: Math.abs(mousePos.x - startX), - height: Math.abs(mousePos.y - startY) + height: Math.abs(mousePos.y - startY), } const selectedNodes = getNodesInside(this.nodes, nextUserSelectRect, this.transform) @@ -231,8 +231,8 @@ export default function configureStore( ...node, __rf: { ...node.__rf, - position: clampPosition(node.__rf.position, nodeExtent) - } + position: clampPosition(node.__rf.position, nodeExtent), + }, } }) }, @@ -255,7 +255,7 @@ export default function configureStore( this.nodesDraggable = isInteractive this.nodesConnectable = isInteractive this.elementsSelectable = isInteractive - } - } + }, + }, }) } diff --git a/src/store/index.ts b/src/store/index.ts index bbb02dbd..9c119735 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -16,12 +16,12 @@ export const initialState: RevueFlowState = { maxZoom: 2, translateExtent: [ [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], - [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY] + [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], ], nodeExtent: [ [Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY], - [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY] + [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY], ], nodesSelectionActive: false, @@ -34,7 +34,7 @@ export const initialState: RevueFlowState = { y: 0, width: 0, height: 0, - draw: false + draw: false, }, connectionNodeId: null, connectionHandleId: null, @@ -51,5 +51,5 @@ export const initialState: RevueFlowState = { multiSelectionActive: false, - revueFlowVersion: typeof __REVUE_FLOW_VERSION__ !== 'undefined' ? __REVUE_FLOW_VERSION__ : '-' + revueFlowVersion: typeof __REVUE_FLOW_VERSION__ !== 'undefined' ? __REVUE_FLOW_VERSION__ : '-', } diff --git a/src/types/edge.ts b/src/types/edge.ts index e1ac5266..5adae564 100644 --- a/src/types/edge.ts +++ b/src/types/edge.ts @@ -1,4 +1,4 @@ -import { ComponentPublicInstance, HTMLAttributes, VNode } from 'vue' +import { DefineComponent, HTMLAttributes, VNode } from 'vue' import { ArrowHeadType, ElementId, Position } from './types' import { Connection } from './connection' @@ -64,6 +64,6 @@ export interface EdgeTextProps extends HTMLAttributes { labelBgBorderRadius?: number } -export type EdgeType = ComponentPublicInstance +export type EdgeType = DefineComponent export type OnEdgeUpdateFunc = (oldEdge: Edge, newConnection: Connection) => void diff --git a/src/types/node.ts b/src/types/node.ts index 9813f548..b4a861d8 100644 --- a/src/types/node.ts +++ b/src/types/node.ts @@ -1,4 +1,4 @@ -import { ComponentPublicInstance } from 'vue' +import { DefineComponent } from 'vue' import { XYPosition } from './types' import { ElementId, Position } from './index' @@ -57,4 +57,4 @@ export interface NodeProps { isDragging?: boolean } -export type NodeType = ComponentPublicInstance +export type NodeType = DefineComponent diff --git a/src/types/types.ts b/src/types/types.ts index fa787f73..01205b88 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -1,5 +1,8 @@ -import { Edge } from './edge' -import { Node } from './node' +import { CSSProperties, HTMLAttributes } from 'vue' +import { Edge, EdgeType } from './edge' +import { Node, NodeExtent, NodeType, TranslateExtent } from './node' +import { ConnectionLineComponent, ConnectionLineType, ConnectionMode } from '~/types/connection' +import { KeyCode, PanOnScrollMode } from '~/types/panel' export type ElementId = string @@ -81,3 +84,41 @@ export type OnLoadParams = { } export type OnLoadFunc = (params: OnLoadParams) => void + +export interface RevueFlowOptions extends Omit { + nodeTypes?: Record + edgeTypes?: Record + connectionMode?: ConnectionMode + connectionLineType?: ConnectionLineType + connectionLineStyle?: CSSProperties + connectionLineComponent?: ConnectionLineComponent + deleteKeyCode?: KeyCode + selectionKeyCode?: KeyCode + multiSelectionKeyCode?: KeyCode + zoomActivationKeyCode?: KeyCode + snapToGrid?: boolean + snapGrid?: [number, number] + onlyRenderVisibleElements?: boolean + nodesDraggable?: boolean + nodesConnectable?: boolean + elementsSelectable?: boolean + selectNodesOnDrag?: boolean + paneMoveable?: boolean + minZoom?: number + maxZoom?: number + defaultZoom?: number + defaultPosition?: [number, number] + translateExtent?: TranslateExtent + nodeExtent?: NodeExtent + arrowHeadColor?: string + markerEndId?: string + zoomOnScroll?: boolean + zoomOnPinch?: boolean + panOnScroll?: boolean + panOnScrollSpeed?: number + panOnScrollMode?: PanOnScrollMode + zoomOnDoubleClick?: boolean + edgeUpdaterRadius?: number + // nodeTypesId?: string + // edgeTypesId?: string / used by react-flow to detect a re-render of node components +} diff --git a/src/utils/graph.ts b/src/utils/graph.ts index 23ed1061..5a600516 100644 --- a/src/utils/graph.ts +++ b/src/utils/graph.ts @@ -1,5 +1,3 @@ -import { clampPosition, clamp } from './index' - import { ElementId, Node, @@ -12,8 +10,9 @@ import { Connection, FlowExportObject, NodeExtent, - RevueFlowStore + RevueFlowStore, } from '../types' +import { clampPosition, clamp } from './index' export const isEdge = (element: Node | Connection | Edge): element is Edge => 'id' in element && 'source' in element && 'target' in element @@ -62,7 +61,7 @@ const connectionExists = (edge: Edge, elements: Elements) => { el.source === edge.source && el.target === edge.target && (el.sourceHandle === edge.sourceHandle || (!el.sourceHandle && !edge.sourceHandle)) && - (el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle)) + (el.targetHandle === edge.targetHandle || (!el.targetHandle && !edge.targetHandle)), ) } @@ -78,7 +77,7 @@ export const addEdge = (edgeParams: Edge | Connection, elements: Elements): Elem } else { edge = { ...edgeParams, - id: getEdgeId(edgeParams) + id: getEdgeId(edgeParams), } as Edge } @@ -109,7 +108,7 @@ export const updateEdge = (oldEdge: Edge, newConnection: Connection, elements: E source: newConnection.source, target: newConnection.target, sourceHandle: newConnection.sourceHandle, - targetHandle: newConnection.targetHandle + targetHandle: newConnection.targetHandle, } as Edge return elements.filter((e) => e.id !== oldEdge.id).concat(edge) @@ -119,75 +118,70 @@ export const pointToRendererPoint = ( { x, y }: XYPosition, [tx, ty, tScale]: Transform, snapToGrid: boolean, - [snapX, snapY]: [number, number] + [snapX, snapY]: [number, number], ): XYPosition => { const position: XYPosition = { x: (x - tx) / tScale, - y: (y - ty) / tScale + y: (y - ty) / tScale, } if (snapToGrid) { return { x: snapX * Math.round(position.x / snapX), - y: snapY * Math.round(position.y / snapY) + y: snapY * Math.round(position.y / snapY), } } return position } -export const onLoadProject = (currentStore: RevueFlowStore) => { - return (position: XYPosition): XYPosition => { - return pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid) - } -} +export const onLoadProject = + (currentStore: RevueFlowStore) => + (position: XYPosition): XYPosition => + pointToRendererPoint(position, currentStore.transform, currentStore.snapToGrid, currentStore.snapGrid) -export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => { - return { - ...node, - id: node.id.toString(), - type: node.type || 'default', - __rf: { - position: clampPosition(node.position, nodeExtent), - width: null, - height: null, - handleBounds: {}, - isDragging: false - } - } -} +export const parseNode = (node: Node, nodeExtent: NodeExtent): Node => ({ + ...node, + id: node.id.toString(), + type: node.type || 'default', + __rf: { + position: clampPosition(node.position, nodeExtent), + width: null, + height: null, + handleBounds: {}, + isDragging: false, + }, +}) -export const parseEdge = (edge: Edge): Edge => { - return { - ...edge, - source: edge.source.toString(), - target: edge.target.toString(), - sourceHandle: edge.sourceHandle ? edge.sourceHandle.toString() : null, - targetHandle: edge.targetHandle ? edge.targetHandle.toString() : null, - id: edge.id.toString(), - type: edge.type || 'default' - } -} +export const parseEdge = (edge: Edge): Edge => ({ + ...edge, + source: edge.source.toString(), + target: edge.target.toString(), + sourceHandle: edge.sourceHandle ? edge.sourceHandle.toString() : null, + targetHandle: edge.targetHandle ? edge.targetHandle.toString() : null, + id: edge.id.toString(), + type: edge.type || 'default', +}) const getBoundsOfBoxes = (box1: Box, box2: Box): Box => ({ x: Math.min(box1.x, box2.x), y: Math.min(box1.y, box2.y), x2: Math.max(box1.x2, box2.x2), - y2: Math.max(box1.y2, box2.y2) + y2: Math.max(box1.y2, box2.y2), }) export const rectToBox = ({ x, y, width, height }: Rect): Box => ({ x, y, x2: x + width, - y2: y + height + y2: y + height, }) export const boxToRect = ({ x, y, x2, y2 }: Box): Rect => ({ x, y, width: x2 - x, - height: y2 - y + height: y2 - y, }) export const getBoundsofRects = (rect1: Rect, rect2: Rect): Rect => @@ -201,10 +195,10 @@ export const getRectOfNodes = (nodes: Node[]): Rect => { rectToBox({ ...position, width, - height - } as any) + height, + } as any), ), - { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity } + { x: Infinity, y: Infinity, x2: -Infinity, y2: -Infinity }, ) return boxToRect(box) @@ -212,7 +206,7 @@ export const getRectOfNodes = (nodes: Node[]): Rect => { export const graphPosToZoomedPos = ({ x, y }: XYPosition, [tx, ty, tScale]: Transform): XYPosition => ({ x: x * tScale + tx, - y: y * tScale + ty + y: y * tScale + ty, }) export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Transform = [0, 0, 1], partially = false): Node[] => { @@ -220,7 +214,7 @@ export const getNodesInside = (nodes: Node[], rect: Rect, [tx, ty, tScale]: Tran x: (rect.x - tx) / tScale, y: (rect.y - ty) / tScale, width: rect.width / tScale, - height: rect.height / tScale + height: rect.height / tScale, }) return nodes.filter(({ __rf: { position, width, height, isDragging } }) => { @@ -250,35 +244,26 @@ export const getConnectedEdges = (nodes: Node[], edges: Edge[]): Edge[] => { return edges.filter((edge) => nodeIds.includes(edge.source) || nodeIds.includes(edge.target)) } -const parseElements = (nodes: Node[], edges: Edge[]): Elements => { - return [ - ...nodes.map((node) => { - const n = { ...node } +const parseElements = (nodes: Node[], edges: Edge[]): Elements => [ + ...nodes.map((node) => { + const n = { ...node } - n.position = n.__rf.position + n.position = n.__rf.position - n.__rf = {} as any - return n - }), - ...edges.map((e) => ({ ...e })) - ] -} + n.__rf = {} as any + return n + }), + ...edges.map((e) => ({ ...e })), +] -export const onLoadGetElements = (currentStore: RevueFlowStore) => { - return (): Elements => { - return parseElements(currentStore.nodes || [], currentStore.edges || []) - } -} +export const onLoadGetElements = (currentStore: RevueFlowStore) => (): Elements => + parseElements(currentStore.nodes || [], currentStore.edges || []) -export const onLoadToObject = (currentStore: RevueFlowStore) => { - return (): FlowExportObject => { - return { - elements: parseElements(currentStore.nodes || [], currentStore.edges || []), - position: [currentStore.transform[0], currentStore.transform[1]], - zoom: currentStore.transform[2] - } - } -} +export const onLoadToObject = (currentStore: RevueFlowStore) => (): FlowExportObject => ({ + elements: parseElements(currentStore.nodes || [], currentStore.edges || []), + position: [currentStore.transform[0], currentStore.transform[1]], + zoom: currentStore.transform[2], +}) export const getTransformForBounds = ( bounds: Rect, @@ -286,7 +271,7 @@ export const getTransformForBounds = ( height: number, minZoom: number, maxZoom: number, - padding = 0.1 + padding = 0.1, ): Transform => { const xZoom = width / (bounds.width * (1 + padding)) const yZoom = height / (bounds.height * (1 + padding)) diff --git a/src/utils/index.ts b/src/utils/index.ts index 1075b4b4..357b97c3 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -5,16 +5,16 @@ export const isInputDOMNode = (e: KeyboardEvent | MouseEvent): boolean => { return ['INPUT', 'SELECT', 'TEXTAREA', 'BUTTON'].includes(target.nodeName) || target.hasAttribute('contentEditable') } -export const getDimensions = (node: HTMLDivElement): Dimensions => ({ +export const getDimensions = (node: HTMLElement): Dimensions => ({ width: node.offsetWidth, - height: node.offsetHeight + height: node.offsetHeight, }) export const clamp = (val: number, min = 0, max = 1): number => Math.min(Math.max(val, min), max) export const clampPosition = (position: XYPosition, extent: NodeExtent): XYPosition => ({ x: clamp(position.x, extent[0][0], extent[1][0]), - y: clamp(position.y, extent[0][1], extent[1][1]) + y: clamp(position.y, extent[0][1], extent[1][1]), }) export const getHostForElement = (element: HTMLElement): Document | ShadowRoot => diff --git a/tsconfig.json b/tsconfig.json index 23334232..0e71b442 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "baseUrl": ".", "module": "ESNext", - "target": "es2016", + "target": "es2020", "lib": [ "DOM", "ESNext"