diff --git a/package-lock.json b/package-lock.json index e40bc5c..6fd6d1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "dependencies": { "@arco-design/color": "^0.4.0", + "@floating-ui/vue": "^2.0.1", "@inertiajs/vue3": "^2.3.4", "@sentry/vue": "^10.43.0", "@vitejs/plugin-vue": "^6.0.2", @@ -17,7 +18,6 @@ "pinia": "^3.0.4", "pusher-js": "^8.5.0", "ufo": "^1.6.1", - "v-onboarding": "^2.12.4", "vue": "^3.5.25", "vue-draggable-next": "^2.3.0", "vue-icons-plus": "^0.1.9", @@ -596,6 +596,44 @@ "node": ">=18" } }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@floating-ui/vue": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-2.0.1.tgz", + "integrity": "sha512-043dyEKD9TDSfz080YH2vobubgg+NIo/9Fw4RAl7sEMpD8Am1DNrrSTl/GpMBjyGHoD/FbJY2QLawY+HSQsEIA==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + }, + "peerDependencies": { + "vue": ">=3.3.0" + } + }, "node_modules/@inertiajs/core": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.4.tgz", @@ -687,16 +725,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.53", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", @@ -1094,8 +1122,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@tailwindcss/language-server": { "version": "0.14.29", @@ -1839,140 +1866,6 @@ "vue": "^3.5.0" } }, - "node_modules/@vueuse/integrations": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.11.1.tgz", - "integrity": "sha512-Y5hCGBguN+vuVYTZmdd/IMXLOdfS60zAmDmFYc4BKBcMUPZH1n4tdyDECCPjXm0bNT3ZRUy1xzTLGaUje8Xyaw==", - "license": "MIT", - "dependencies": { - "@vueuse/core": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^4", - "drauu": "^0.3", - "focus-trap": "^7", - "fuse.js": "^6", - "idb-keyval": "^6", - "jwt-decode": "^3", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^6" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/integrations/node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, - "node_modules/@vueuse/integrations/node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations/node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations/node_modules/@vueuse/shared": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", - "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==", - "license": "MIT", - "dependencies": { - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, "node_modules/@vueuse/metadata": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.1.0.tgz", @@ -2044,13 +1937,14 @@ "version": "5.13.0", "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.13.0.tgz", "integrity": "sha512-PJuXT6zdiCbv0IkX5cqkKFVIIh+9v3kqP9zsOHEGpIWi7DfTgzvfOKc8icw6G3/ulR3V1alDDUtOVH0zWCWGEQ==", - "license": "SEE LICENSE IN LICENSE" + "license": "SEE LICENSE IN LICENSE", + "peer": true }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/asynckit": { @@ -2275,6 +2169,7 @@ "integrity": "sha512-9nF4PdUle+5ta4W5SyZdLCCmFd37uVimSjg1evcTqKJCyvCEEj12WKzOSBNak6r4im4J4iYXKH1OWpUV5LBYFg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@emotion/hash": "~0.8.0", "csstype": "~3.0.5" @@ -2350,6 +2245,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -2403,6 +2299,7 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", "license": "MIT", + "peer": true, "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -2423,7 +2320,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -2498,7 +2394,6 @@ "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", @@ -2512,7 +2407,6 @@ "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" } @@ -2669,15 +2563,6 @@ } } }, - "node_modules/focus-trap": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", - "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", - "license": "MIT", - "dependencies": { - "tabbable": "^6.4.0" - } - }, "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", @@ -3298,8 +3183,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/muggle-string": { "version": "0.4.1", @@ -3417,6 +3301,7 @@ "resolved": "https://registry.npmjs.org/pinia/-/pinia-3.0.4.tgz", "integrity": "sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==", "license": "MIT", + "peer": true, "dependencies": { "@vue/devtools-api": "^7.7.7" }, @@ -3502,6 +3387,7 @@ "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-8.5.0.tgz", "integrity": "sha512-V7uzGi9bqOOOyM/6IkJdpFyjGZj7llz1v0oWnYkZKcYLvbz6VcHVLmzKqkvegjuMumpfIEKGLmWHwFb39XFCpw==", "license": "MIT", + "peer": true, "dependencies": { "tweetnacl": "^1.0.3" } @@ -3742,7 +3628,6 @@ "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", "license": "MIT", - "peer": true, "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" @@ -3832,12 +3717,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/tabbable": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.5.0.tgz", - "integrity": "sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==", - "license": "MIT" - }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -3943,21 +3822,6 @@ "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", "license": "MIT" }, - "node_modules/v-onboarding": { - "version": "2.12.4", - "resolved": "https://registry.npmjs.org/v-onboarding/-/v-onboarding-2.12.4.tgz", - "integrity": "sha512-alfR0YSbE5v37JhrsDgxOxTIuHjBrVnwIWmwqvTtp0p66FSI/E5IEXgEOioelCd6RipwIUyZU4yeHocGzAIz2g==", - "license": "MIT", - "dependencies": { - "@popperjs/core": "^2.11.5", - "@vueuse/integrations": "^10.7.2", - "focus-trap": "^7", - "vue": "^3.2.21" - }, - "peerDependencies": { - "vue": "^3.2.21" - } - }, "node_modules/vdirs": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/vdirs/-/vdirs-0.1.8.tgz", @@ -3976,6 +3840,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4333,6 +4198,7 @@ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", "integrity": "sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==", "license": "MIT", + "peer": true, "dependencies": { "@vue/compiler-dom": "3.5.26", "@vue/compiler-sfc": "3.5.26", @@ -4445,7 +4311,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, @@ -4466,7 +4331,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "peer": true, "engines": { "node": ">=0.4.0" } diff --git a/package.json b/package.json index e9a7945..3a87a88 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ }, "dependencies": { "@arco-design/color": "^0.4.0", + "@floating-ui/vue": "^2.0.1", "@inertiajs/vue3": "^2.3.4", "@sentry/vue": "^10.43.0", "@vitejs/plugin-vue": "^6.0.2", @@ -30,7 +31,6 @@ "pinia": "^3.0.4", "pusher-js": "^8.5.0", "ufo": "^1.6.1", - "v-onboarding": "^2.12.4", "vue": "^3.5.25", "vue-draggable-next": "^2.3.0", "vue-icons-plus": "^0.1.9", diff --git a/resources/js/Components/Onboarding/Onboarding.vue b/resources/js/Components/Onboarding/Onboarding.vue deleted file mode 100644 index cc7a203..0000000 --- a/resources/js/Components/Onboarding/Onboarding.vue +++ /dev/null @@ -1,118 +0,0 @@ - - - - - diff --git a/resources/js/Components/Tour/Tour.vue b/resources/js/Components/Tour/Tour.vue new file mode 100644 index 0000000..43b0c11 --- /dev/null +++ b/resources/js/Components/Tour/Tour.vue @@ -0,0 +1,159 @@ + + + + \ No newline at end of file diff --git a/resources/js/Components/Tour/TourOverlay.vue b/resources/js/Components/Tour/TourOverlay.vue new file mode 100644 index 0000000..36bc448 --- /dev/null +++ b/resources/js/Components/Tour/TourOverlay.vue @@ -0,0 +1,71 @@ + + + + + + + \ No newline at end of file diff --git a/resources/js/Components/Tour/TourTooltip.vue b/resources/js/Components/Tour/TourTooltip.vue new file mode 100644 index 0000000..efa89f3 --- /dev/null +++ b/resources/js/Components/Tour/TourTooltip.vue @@ -0,0 +1,154 @@ + + + + + \ No newline at end of file diff --git a/resources/js/Components/Tour/composables/useFloating.js b/resources/js/Components/Tour/composables/useFloating.js new file mode 100644 index 0000000..5468734 --- /dev/null +++ b/resources/js/Components/Tour/composables/useFloating.js @@ -0,0 +1,125 @@ +import { ref, watch, onBeforeUnmount, unref, nextTick } from "vue"; + +import { + computePosition, + autoUpdate, + offset, + flip, + shift +} from "@floating-ui/dom"; + +export function useFloating(target, floating, options = {}) { + const x = ref(0); + const y = ref(0); + + const strategy = ref("fixed"); + + const placement = ref( + options.placement ?? "bottom" + ); + + let cleanup = null; + + function resolveTarget() { + const value = unref(target); + + if (!value) + return null; + + if (typeof value === "function") + return value(); + + if (typeof value === "string") + return document.querySelector(value); + + return value; + } + + function resolveFloating() { + return unref(floating); + } + + async function update() { + const reference = resolveTarget(); + const popup = resolveFloating(); + + if (!reference || !popup) + return; + + const result = await computePosition( + reference, + popup, + { + strategy: strategy.value, + placement: placement.value, + middleware: [ + offset( + options.offset ?? 12 + ), + flip(), + shift({ + padding: 8 + }) + ] + + } + ) + + x.value = result.x; + y.value = result.y; + } + + function start() { + cleanup?.(); + const reference = resolveTarget(); + const popup = resolveFloating(); + + if (!reference || !popup) + return; + + cleanup = autoUpdate( + reference, + popup, + update + ); + + update(); + } + + watch( + [ + () => resolveTarget(), + () => resolveFloating() + ], + () => { + start() + }, + { + immediate:true + } + ) + + watch( + floating, + async value => { + + if (!value) + return + + await nextTick() + update() + } + ) + + onBeforeUnmount(() => { + cleanup?.(); + }); + + return { + x, + y, + strategy, + update + }; + +} \ No newline at end of file diff --git a/resources/js/Components/Tour/composables/useHighlight.js b/resources/js/Components/Tour/composables/useHighlight.js new file mode 100644 index 0000000..035681c --- /dev/null +++ b/resources/js/Components/Tour/composables/useHighlight.js @@ -0,0 +1,138 @@ +import { + ref, + computed, + watch, + onMounted, + onBeforeUnmount, + unref +} from "vue"; + + +export function useHighlight(target, options = {}) { + const rect = ref(null); + const padding = options.padding ?? 8; + + let resizeObserver = null; + + function resolveTarget() { + const value = unref(target); + + if (!value) + return null; + + if (typeof value === "function") + return value(); + + if (typeof value === "string") + return document.querySelector(value); + + return value; + } + + + function update() { + const element = resolveTarget(); + + if (!element) { + rect.value = null; + return; + } + + const box = element.getBoundingClientRect(); + + rect.value = { + top: box.top - padding, + left: box.left - padding, + width: box.width + padding * 2, + height: box.height + padding * 2, + bottom: + box.bottom + padding, + right: + box.right + padding + }; + + } + + function observe() { + const element = resolveTarget(); + + if (!element) + return; + + resizeObserver?.disconnect(); + resizeObserver = new ResizeObserver(() => { + update(); + }); + + resizeObserver.observe(element); + } + + + const style = computed(() => { + + if (!rect.value) + return {}; + + return { + top: + `${rect.value.top}px`, + left: + `${rect.value.left}px`, + width: + `${rect.value.width}px`, + height: + `${rect.value.height}px` + }; + }); + + watch( + () => unref(target), + () => { + update(); + observe(); + }, + { + immediate: true + } + ); + + onMounted(() => { + window.addEventListener( + "resize", + update + ); + + window.addEventListener( + "scroll", + update, + true + ); + + update(); + observe(); + }); + + onBeforeUnmount(() => { + resizeObserver?.disconnect(); + + window.removeEventListener( + "resize", + update + ); + + window.removeEventListener( + "scroll", + update, + true + ); + }); + + + + return { + rect, + style, + update + }; + +} \ No newline at end of file diff --git a/resources/js/Components/Tour/composables/useWaitTarget.js b/resources/js/Components/Tour/composables/useWaitTarget.js new file mode 100644 index 0000000..00449af --- /dev/null +++ b/resources/js/Components/Tour/composables/useWaitTarget.js @@ -0,0 +1,61 @@ +import { nextTick } from "vue" + +export function useWaitTarget() { + async function waitTarget(target, options = {}) { + const { + timeout = 5000, + interval = 50, + visible = false + } = options + + const start = Date.now() + + while (Date.now() - start < timeout) { + await nextTick() + let element = null + + if (typeof target === "function") { + element = target() + } + else { + element = document.querySelector(target) + } + + if (element) { + + if (visible) { + const rect = element.getBoundingClientRect() + + if ( + rect.width === 0 || + rect.height === 0 + ) { + await sleep(interval) + continue + } + } + + return element + } + + await sleep(interval) + } + + console.warn( + `[Tour] Target not found:`, + target + ) + + return null + } + + return { + waitTarget + } +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms) + }) +} \ No newline at end of file diff --git a/resources/js/Layouts/AppLayout.vue b/resources/js/Layouts/AppLayout.vue index e2cc40f..7555f4e 100644 --- a/resources/js/Layouts/AppLayout.vue +++ b/resources/js/Layouts/AppLayout.vue @@ -6,7 +6,7 @@ import AppDialogManager from "../Components/AppDialogManager.vue"; import Noise from "../Components/Noise.vue"; import { useThemeVars } from "naive-ui"; import { computed } from "vue"; -import Onboarding from "../Components/Onboarding/Onboarding.vue"; +import Tour from "../Components/Tour/Tour.vue"; const {isGlobalLoading} = useGlobalLoading() const themeVars = useThemeVars() @@ -92,7 +92,7 @@ const blobBg = computed(() => { - +