From 55f2a5cc2d0a782bae3533ba996a848d7cb0337b Mon Sep 17 00:00:00 2001 From: Abdelrahman Ashraf Date: Mon, 20 Jan 2025 18:53:40 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20dotLottie=20v2=20(#89)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 dotLottie v2 --------- Co-authored-by: samuelOsborne --- .changeset/config.json | 2 +- .changeset/many-emus-cough.md | 5 - .changeset/shiny-cherries-float.md | 5 + .changeset/tricky-drinks-care.md | 5 - .github/workflows/beta.yml | 84 + .github/workflows/main.yml | 14 +- .prettierignore | 4 +- apps/next/package.json | 4 +- apps/next/src/pages/api/create-dotlottie.ts | 6 +- apps/next/src/pages/index.tsx | 4 - apps/next/src/pages/ssProps.tsx | 6 +- apps/node/audio_test.mjs | 4 - apps/node/lf-interactivity-page-generator.mjs | 4 - apps/node/package.json | 5 +- apps/node/script.mjs | 1248 ---- apps/node/state_machines.mjs | 1051 ++++ apps/nuxt/.gitignore | 9 - apps/nuxt/.npmrc | 1 - apps/nuxt/README.md | 42 - apps/nuxt/app.vue | 39 - apps/nuxt/nuxt.config.ts | 2 - apps/nuxt/package.json | 18 - apps/nuxt/public/favicon.ico | Bin 4286 -> 0 bytes apps/nuxt/tsconfig.json | 4 - apps/player/index.html | 65 - apps/react/package.json | 4 +- apps/react/src/App.tsx | 4 - apps/vue/package.json | 4 +- apps/vue/src/components/HelloWorld.vue | 4 - package.json | 7 +- packages/dotlottie-js/CHANGELOG.md | 120 + .../dotlottie-js/jasmine/jasmine-browser.json | 12 - packages/dotlottie-js/jasmine/jasmine.json | 7 - packages/dotlottie-js/jasmine/tsup.config.js | 28 - packages/dotlottie-js/package.json | 48 +- packages/dotlottie-js/playground.js | 68 + .../audio/2_instrument_animations.lottie | Bin .../__fixtures__/audio/instruments_1.json | 0 .../__fixtures__/audio/instruments_2.json | 0 .../src/__tests__/__fixtures__/ball.json | 738 +++ .../__fixtures__}/bull.json | 0 .../image-asset-optimization/bull.json | 0 .../image-animation-layer-1.json | 0 .../image-animation-layer-2-3-4-5.json | 0 .../image-animation-layer-2-3-4.json | 0 .../image-animation-layer-2-3.json | 0 .../image-animation-layer-2.json | 0 .../lots-of-dupes.json | 0 .../simple-image-animation.json | 0 .../__fixtures__/mimetype-tests/mp-3-test.txt | 0 .../mimetype-tests/svg-xml-test.txt | 0 .../__fixtures__/simple/animation.lottie | Bin .../simple/animation/animations/bull.json | 1 + .../simple/animation/animations/lottie1.json | 0 .../simple/animation/animations/pigeon.json | 0 .../simple/animation/animations/smiley.json | 0 .../simple/animation/animations/wifi.json | 0 .../simple/animation/manifest.json | 13 +- .../simple/big-merged-dotlottie.lottie | Bin .../__fixtures__/simple/bull.lottie | Bin .../simple/edited-settings.lottie | Bin .../edited-settings/animations/lottie01.json | 0 .../simple/edited-settings/manifest.json | 7 +- .../simple/exploding_pigeon.lottie | Bin .../__fixtures__/simple/state/pigeon-state.ts | 60 +- .../__fixtures__/simple/video-embedded.lottie | Bin .../src/__tests__/browser/index.spec.ts | 224 + .../src/__tests__/node/index.spec.ts | 224 + .../src/common/dotlottie-theme-common.ts | 130 - packages/dotlottie-js/src/common/index.ts | 15 - packages/dotlottie-js/src/constants.ts | 7 + packages/dotlottie-js/src/index.browser.ts | 26 + packages/dotlottie-js/src/index.node.ts | 26 + packages/dotlottie-js/src/index.ts | 11 - packages/dotlottie-js/src/node/index.ts | 10 - .../simple/animation/themes/theme1.json | 14 - .../src/tests/dotlottie-js-browser.spec.ts | 1411 ----- .../src/tests/dotlottie-js-node.spec.ts | 1427 ----- .../tests/lottie-animation-browser.spec.ts | 310 - .../src/tests/lottie-animation-node.spec.ts | 311 - .../src/tests/lottie-theme-browser.spec.ts | 201 - .../src/tests/lottie-theme-node.spec.ts | 201 - packages/dotlottie-js/src/tests/test-utils.ts | 71 - .../src/tests/utils-browser.spec.ts | 363 -- .../dotlottie-js/src/tests/utils-node.spec.ts | 371 -- packages/dotlottie-js/src/types.ts | 23 + .../dotlottie-js/src/{common => }/utils.ts | 254 +- .../v1/__tests__/browser/animation.spec.ts | 290 + .../src/v1/__tests__/browser/audio.spec.ts | 108 + .../v1/__tests__/browser/dotlottie.spec.ts | 854 +++ .../src/v1/__tests__/browser/image.spec.ts | 321 + .../src/v1/__tests__/node/animation.spec.ts | 306 + .../src/v1/__tests__/node/audio.spec.ts | 108 + .../src/v1/__tests__/node/dotlottie.spec.ts | 847 +++ .../src/v1/__tests__/node/image.spec.ts | 321 + .../dotlottie-js/src/v1/browser/animation.ts | 135 + packages/dotlottie-js/src/v1/browser/audio.ts | 12 + .../dotlottie-js/src/v1/browser/dotlottie.ts | 343 ++ packages/dotlottie-js/src/v1/browser/image.ts | 12 + packages/dotlottie-js/src/v1/browser/index.ts | 8 + .../plugins/duplicate-image-detector.ts | 26 + .../dotlottie-js/src/v1/common/animation.ts | 540 ++ packages/dotlottie-js/src/v1/common/audio.ts | 214 + .../dotlottie-js/src/v1/common/dotlottie.ts | 588 ++ packages/dotlottie-js/src/v1/common/image.ts | 238 + packages/dotlottie-js/src/v1/common/index.ts | 9 + packages/dotlottie-js/src/v1/common/plugin.ts | 50 + .../plugins/duplicate-image-detector.ts | 179 + .../src/v1/common/schemas/index.ts | 5 + .../{common => v1/common/schemas}/manifest.ts | 33 +- packages/dotlottie-js/src/v1/index.browser.ts | 6 + packages/dotlottie-js/src/v1/index.node.ts | 6 + .../dotlottie-js/src/v1/node/animation.ts | 129 + packages/dotlottie-js/src/v1/node/audio.ts | 12 + .../dotlottie-js/src/v1/node/dotlottie.ts | 324 + packages/dotlottie-js/src/v1/node/image.ts | 12 + packages/dotlottie-js/src/v1/node/index.ts | 8 + .../node/plugins/duplicate-image-detector.ts | 39 + .../v2/__tests__/browser/animation.spec.ts | 291 + .../__tests__/browser/audio.spec.ts} | 25 +- .../v2/__tests__/browser/dotlottie.spec.ts | 891 +++ .../__tests__/browser/image.spec.ts} | 89 +- .../__tests__/browser/state-machine.spec.ts} | 82 +- .../src/v2/__tests__/node/animation.spec.ts | 305 + .../__tests__/node/audio.spec.ts} | 25 +- .../src/v2/__tests__/node/dotlottie.spec.ts | 891 +++ .../__tests__/node/image.spec.ts} | 58 +- .../__tests__/node/state-machine.spec.ts} | 103 +- .../browser/animation.ts} | 30 +- .../lottie-audio.ts => v2/browser/audio.ts} | 0 .../src/{ => v2/browser}/dotlottie.ts | 192 +- .../lottie-image.ts => v2/browser/image.ts} | 0 packages/dotlottie-js/src/v2/browser/index.ts | 10 + .../plugins}/duplicate-image-detector.ts | 4 +- .../browser/state-machine.ts} | 2 +- .../lottie-theme.ts => v2/browser/theme.ts} | 0 .../common/animation.ts} | 213 +- .../common/audio.ts} | 22 +- .../common/dotlottie.ts} | 373 +- .../common/image.ts} | 51 +- packages/dotlottie-js/src/v2/common/index.ts | 11 + .../common/plugin.ts} | 9 +- .../plugins/duplicate-image-detector.ts} | 30 +- .../src/v2/common/schemas/index.ts | 7 + .../src/v2/common/schemas/manifest.ts | 42 + .../common/schemas/state-machine.ts} | 16 +- .../src/v2/common/schemas/theme.ts | 110 + .../common/state-machine.ts} | 93 +- packages/dotlottie-js/src/v2/common/theme.ts | 92 + packages/dotlottie-js/src/v2/index.browser.ts | 6 + packages/dotlottie-js/src/v2/index.node.ts | 6 + .../node/animation.ts} | 12 +- .../src/{lottie-audio.ts => v2/node/audio.ts} | 4 +- .../src/{ => v2}/node/dotlottie.ts | 212 +- .../src/{lottie-image.ts => v2/node/image.ts} | 4 +- packages/dotlottie-js/src/v2/node/index.ts | 10 + .../node/plugins}/duplicate-image-detector.ts | 7 +- .../node/state-machine.ts} | 0 .../src/{lottie-theme.ts => v2/node/theme.ts} | 4 +- packages/dotlottie-js/tsconfig.json | 7 +- packages/dotlottie-js/tsup.config.cjs | 8 +- packages/dotlottie-js/types/index.d.ts | 4 +- .../dotlottie-js/vitest.browser.config.js | 22 + packages/dotlottie-js/vitest.config.js | 15 + pnpm-lock.yaml | 5478 ++++++----------- turbo.json | 1 - 166 files changed, 14212 insertions(+), 11124 deletions(-) delete mode 100644 .changeset/many-emus-cough.md create mode 100644 .changeset/shiny-cherries-float.md delete mode 100644 .changeset/tricky-drinks-care.md create mode 100644 .github/workflows/beta.yml delete mode 100644 apps/node/script.mjs create mode 100644 apps/node/state_machines.mjs delete mode 100644 apps/nuxt/.gitignore delete mode 100644 apps/nuxt/.npmrc delete mode 100644 apps/nuxt/README.md delete mode 100644 apps/nuxt/app.vue delete mode 100644 apps/nuxt/nuxt.config.ts delete mode 100644 apps/nuxt/package.json delete mode 100644 apps/nuxt/public/favicon.ico delete mode 100644 apps/nuxt/tsconfig.json delete mode 100644 apps/player/index.html delete mode 100644 packages/dotlottie-js/jasmine/jasmine-browser.json delete mode 100644 packages/dotlottie-js/jasmine/jasmine.json delete mode 100644 packages/dotlottie-js/jasmine/tsup.config.js create mode 100644 packages/dotlottie-js/playground.js rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/audio/2_instrument_animations.lottie (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/audio/instruments_1.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/audio/instruments_2.json (100%) create mode 100644 packages/dotlottie-js/src/__tests__/__fixtures__/ball.json rename packages/dotlottie-js/src/{tests/__fixtures__/simple/animation/animations => __tests__/__fixtures__}/bull.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/bull.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/image-animation-layer-1.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/image-animation-layer-2.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/lots-of-dupes.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/image-asset-optimization/simple-image-animation.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/mimetype-tests/mp-3-test.txt (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/mimetype-tests/svg-xml-test.txt (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation.lottie (100%) create mode 100644 packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/bull.json rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation/animations/lottie1.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation/animations/pigeon.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation/animations/smiley.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation/animations/wifi.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/animation/manifest.json (58%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/big-merged-dotlottie.lottie (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/bull.lottie (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/edited-settings.lottie (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/edited-settings/animations/lottie01.json (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/edited-settings/manifest.json (79%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/exploding_pigeon.lottie (100%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/state/pigeon-state.ts (84%) rename packages/dotlottie-js/src/{tests => __tests__}/__fixtures__/simple/video-embedded.lottie (100%) create mode 100644 packages/dotlottie-js/src/__tests__/browser/index.spec.ts create mode 100644 packages/dotlottie-js/src/__tests__/node/index.spec.ts delete mode 100644 packages/dotlottie-js/src/common/dotlottie-theme-common.ts delete mode 100644 packages/dotlottie-js/src/common/index.ts create mode 100644 packages/dotlottie-js/src/constants.ts create mode 100644 packages/dotlottie-js/src/index.browser.ts create mode 100644 packages/dotlottie-js/src/index.node.ts delete mode 100644 packages/dotlottie-js/src/index.ts delete mode 100644 packages/dotlottie-js/src/node/index.ts delete mode 100644 packages/dotlottie-js/src/tests/__fixtures__/simple/animation/themes/theme1.json delete mode 100644 packages/dotlottie-js/src/tests/dotlottie-js-browser.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/dotlottie-js-node.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/lottie-animation-browser.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/lottie-animation-node.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/lottie-theme-browser.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/lottie-theme-node.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/test-utils.ts delete mode 100644 packages/dotlottie-js/src/tests/utils-browser.spec.ts delete mode 100644 packages/dotlottie-js/src/tests/utils-node.spec.ts create mode 100644 packages/dotlottie-js/src/types.ts rename packages/dotlottie-js/src/{common => }/utils.ts (82%) create mode 100644 packages/dotlottie-js/src/v1/__tests__/browser/animation.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/browser/audio.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/browser/dotlottie.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/browser/image.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/node/animation.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/node/audio.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/node/dotlottie.spec.ts create mode 100644 packages/dotlottie-js/src/v1/__tests__/node/image.spec.ts create mode 100644 packages/dotlottie-js/src/v1/browser/animation.ts create mode 100644 packages/dotlottie-js/src/v1/browser/audio.ts create mode 100644 packages/dotlottie-js/src/v1/browser/dotlottie.ts create mode 100644 packages/dotlottie-js/src/v1/browser/image.ts create mode 100644 packages/dotlottie-js/src/v1/browser/index.ts create mode 100644 packages/dotlottie-js/src/v1/browser/plugins/duplicate-image-detector.ts create mode 100644 packages/dotlottie-js/src/v1/common/animation.ts create mode 100644 packages/dotlottie-js/src/v1/common/audio.ts create mode 100644 packages/dotlottie-js/src/v1/common/dotlottie.ts create mode 100644 packages/dotlottie-js/src/v1/common/image.ts create mode 100644 packages/dotlottie-js/src/v1/common/index.ts create mode 100644 packages/dotlottie-js/src/v1/common/plugin.ts create mode 100644 packages/dotlottie-js/src/v1/common/plugins/duplicate-image-detector.ts create mode 100644 packages/dotlottie-js/src/v1/common/schemas/index.ts rename packages/dotlottie-js/src/{common => v1/common/schemas}/manifest.ts (60%) create mode 100644 packages/dotlottie-js/src/v1/index.browser.ts create mode 100644 packages/dotlottie-js/src/v1/index.node.ts create mode 100644 packages/dotlottie-js/src/v1/node/animation.ts create mode 100644 packages/dotlottie-js/src/v1/node/audio.ts create mode 100644 packages/dotlottie-js/src/v1/node/dotlottie.ts create mode 100644 packages/dotlottie-js/src/v1/node/image.ts create mode 100644 packages/dotlottie-js/src/v1/node/index.ts create mode 100644 packages/dotlottie-js/src/v1/node/plugins/duplicate-image-detector.ts create mode 100644 packages/dotlottie-js/src/v2/__tests__/browser/animation.spec.ts rename packages/dotlottie-js/src/{tests/lottie-audio-browser.spec.ts => v2/__tests__/browser/audio.spec.ts} (77%) create mode 100644 packages/dotlottie-js/src/v2/__tests__/browser/dotlottie.spec.ts rename packages/dotlottie-js/src/{tests/lottie-image-node.spec.ts => v2/__tests__/browser/image.spec.ts} (75%) rename packages/dotlottie-js/src/{tests/lottie-state-browser.spec.ts => v2/__tests__/browser/state-machine.spec.ts} (57%) create mode 100644 packages/dotlottie-js/src/v2/__tests__/node/animation.spec.ts rename packages/dotlottie-js/src/{tests/lottie-audio-node.spec.ts => v2/__tests__/node/audio.spec.ts} (77%) create mode 100644 packages/dotlottie-js/src/v2/__tests__/node/dotlottie.spec.ts rename packages/dotlottie-js/src/{tests/lottie-image-browser.spec.ts => v2/__tests__/node/image.spec.ts} (80%) rename packages/dotlottie-js/src/{tests/lottie-state-node.spec.ts => v2/__tests__/node/state-machine.spec.ts} (50%) rename packages/dotlottie-js/src/{lottie-animation.ts => v2/browser/animation.ts} (75%) rename packages/dotlottie-js/src/{node/lottie-audio.ts => v2/browser/audio.ts} (100%) rename packages/dotlottie-js/src/{ => v2/browser}/dotlottie.ts (62%) rename packages/dotlottie-js/src/{node/lottie-image.ts => v2/browser/image.ts} (100%) create mode 100644 packages/dotlottie-js/src/v2/browser/index.ts rename packages/dotlottie-js/src/{ => v2/browser/plugins}/duplicate-image-detector.ts (82%) rename packages/dotlottie-js/src/{lottie-state-machine.ts => v2/browser/state-machine.ts} (88%) rename packages/dotlottie-js/src/{node/lottie-theme.ts => v2/browser/theme.ts} (100%) rename packages/dotlottie-js/src/{common/lottie-animation-common.ts => v2/common/animation.ts} (64%) rename packages/dotlottie-js/src/{common/lottie-audio-common.ts => v2/common/audio.ts} (90%) rename packages/dotlottie-js/src/{common/dotlottie-common.ts => v2/common/dotlottie.ts} (60%) rename packages/dotlottie-js/src/{common/lottie-image-common.ts => v2/common/image.ts} (74%) create mode 100644 packages/dotlottie-js/src/v2/common/index.ts rename packages/dotlottie-js/src/{common/dotlottie-plugin.ts => v2/common/plugin.ts} (74%) rename packages/dotlottie-js/src/{common/duplicate-image-detector-common.ts => v2/common/plugins/duplicate-image-detector.ts} (87%) create mode 100644 packages/dotlottie-js/src/v2/common/schemas/index.ts create mode 100644 packages/dotlottie-js/src/v2/common/schemas/manifest.ts rename packages/dotlottie-js/src/{common/dotlottie-state.ts => v2/common/schemas/state-machine.ts} (95%) create mode 100644 packages/dotlottie-js/src/v2/common/schemas/theme.ts rename packages/dotlottie-js/src/{common/dotlottie-state-machine-common.ts => v2/common/state-machine.ts} (68%) create mode 100644 packages/dotlottie-js/src/v2/common/theme.ts create mode 100644 packages/dotlottie-js/src/v2/index.browser.ts create mode 100644 packages/dotlottie-js/src/v2/index.node.ts rename packages/dotlottie-js/src/{node/lottie-animation.ts => v2/node/animation.ts} (91%) rename packages/dotlottie-js/src/{lottie-audio.ts => v2/node/audio.ts} (64%) rename packages/dotlottie-js/src/{ => v2}/node/dotlottie.ts (59%) rename packages/dotlottie-js/src/{lottie-image.ts => v2/node/image.ts} (64%) create mode 100644 packages/dotlottie-js/src/v2/node/index.ts rename packages/dotlottie-js/src/{node => v2/node/plugins}/duplicate-image-detector.ts (74%) rename packages/dotlottie-js/src/{node/lottie-state-machine.ts => v2/node/state-machine.ts} (100%) rename packages/dotlottie-js/src/{lottie-theme.ts => v2/node/theme.ts} (64%) create mode 100644 packages/dotlottie-js/vitest.browser.config.js create mode 100644 packages/dotlottie-js/vitest.config.js diff --git a/.changeset/config.json b/.changeset/config.json index 6d2119a4..f5548209 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,5 +7,5 @@ "access": "restricted", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": ["node-example", "next-example", "react-example", "vue-example"] } diff --git a/.changeset/many-emus-cough.md b/.changeset/many-emus-cough.md deleted file mode 100644 index 8bc6a513..00000000 --- a/.changeset/many-emus-cough.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@dotlottie/dotlottie-js": patch ---- - -chore: bump lottie-types pkg to v1.2.0 diff --git a/.changeset/shiny-cherries-float.md b/.changeset/shiny-cherries-float.md new file mode 100644 index 00000000..b576425f --- /dev/null +++ b/.changeset/shiny-cherries-float.md @@ -0,0 +1,5 @@ +--- +'@dotlottie/dotlottie-js': major +--- + +added support for v2 dotLottie files. Added retro-compatibility for v1 dotLotties. diff --git a/.changeset/tricky-drinks-care.md b/.changeset/tricky-drinks-care.md deleted file mode 100644 index 95bc84b5..00000000 --- a/.changeset/tricky-drinks-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@dotlottie/dotlottie-js": patch ---- - -fixes issues with wrong images ids diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml new file mode 100644 index 00000000..f5fe59dc --- /dev/null +++ b/.github/workflows/beta.yml @@ -0,0 +1,84 @@ +name: beta +on: + push: + branches: + - 'beta' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - name: 🛑 Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.9.0 + + - name: ⬇️ Checkout repo + uses: actions/checkout@v2 + + - name: ⎔ Setup pnpm@9 + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: ⎔ Setup Node@18 + uses: actions/setup-node@v3 + with: + cache: 'pnpm' + node-version: 18 + + - name: 📥 Download deps + run: pnpm install + + - name: 🏗 Build + run: pnpm build + + - name: 🔍 Verify types + run: pnpm type-check + + - name: 💅 Verify format (`pnpm format` committed?) + run: pnpm format --check --no-write + + - name: 🕵️ Lint + run: pnpm lint + + - name: 🛡️ Test + run: | + npx playwright install --with-deps + pnpm test + + - name: 📏 Report bundle size + uses: andresz1/size-limit-action@v1 + continue-on-error: true + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + + release: + needs: validate + runs-on: ubuntu-latest + if: ${{ github.repository == 'dotlottie/dotlottie-js' && github.event_name == 'push' }} + steps: + - name: ⬇️ Checkout repo + uses: actions/checkout@v2 + with: + token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} + + - name: ⎔ Setup pnpm@9 + uses: pnpm/action-setup@v2 + with: + version: 9 + + - name: ⎔ Setup Node@18 + uses: actions/setup-node@v3 + with: + cache: 'pnpm' + node-version: 18 + + - name: 📥 Download deps + run: pnpm install + + - name: 🏗 Build + run: pnpm build + working-directory: packages/dotlottie-js diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 4fe1ae0b..21b05599 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,7 +3,6 @@ on: push: branches: - 'main' - pull_request: concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -19,10 +18,10 @@ jobs: - name: ⬇️ Checkout repo uses: actions/checkout@v2 - - name: ⎔ Setup pnpm@8 + - name: ⎔ Setup pnpm@9 uses: pnpm/action-setup@v2 with: - version: 8 + version: 9 - name: ⎔ Setup Node@18 uses: actions/setup-node@v3 @@ -46,7 +45,9 @@ jobs: run: pnpm lint - name: 🛡️ Test - run: pnpm test + run: | + npx playwright install --with-deps + pnpm test - name: 📏 Report bundle size uses: andresz1/size-limit-action@v1 @@ -64,10 +65,10 @@ jobs: with: token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} - - name: ⎔ Setup pnpm@8 + - name: ⎔ Setup pnpm@9 uses: pnpm/action-setup@v2 with: - version: 8 + version: 9 - name: ⎔ Setup Node@18 uses: actions/setup-node@v3 @@ -80,6 +81,7 @@ jobs: - name: 🏗 Build run: pnpm build + working-directory: packages/dotlottie-js - name: 🚀 Release uses: changesets/action@v1 diff --git a/.prettierignore b/.prettierignore index 944dc8d2..0c4245ab 100644 --- a/.prettierignore +++ b/.prettierignore @@ -40,4 +40,6 @@ releases/ .next/ .nuxt/ -apps/node/ \ No newline at end of file +apps/node/ + +.changeset/ \ No newline at end of file diff --git a/apps/next/package.json b/apps/next/package.json index 3ff2ae05..787ec2a3 100644 --- a/apps/next/package.json +++ b/apps/next/package.json @@ -1,5 +1,5 @@ { - "name": "next", + "name": "next-example", "version": "0.1.0", "private": true, "scripts": { @@ -9,7 +9,7 @@ "start": "next start" }, "dependencies": { - "@dotlottie/dotlottie-js": "workspace:^", + "@dotlottie/dotlottie-js": "workspace:*", "@dotlottie/player-component": "1.3.2", "@types/node": "18.0.6", "@types/react": "18.0.37", diff --git a/apps/next/src/pages/api/create-dotlottie.ts b/apps/next/src/pages/api/create-dotlottie.ts index 3f930c70..c692a3d4 100644 --- a/apps/next/src/pages/api/create-dotlottie.ts +++ b/apps/next/src/pages/api/create-dotlottie.ts @@ -2,7 +2,7 @@ * Copyright 2023 Design Barn Inc. */ -import { DotLottie } from '@dotlottie/dotlottie-js/node'; +import { DotLottie } from '@dotlottie/dotlottie-js'; import type { NextApiRequest, NextApiResponse } from 'next'; interface Data { @@ -14,18 +14,14 @@ export default async function handler(_req: NextApiRequest, res: NextApiResponse let dotLottieAsBuffer = null; await dotLottie - .setAuthor('Joe') - .setVersion('1.0') .addAnimation({ id: 'animation_1', // eslint-disable-next-line no-secrets/no-secrets url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - autoplay: true, }) .addAnimation({ id: 'animation_2', url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, }) .build() .then(async (value) => { diff --git a/apps/next/src/pages/index.tsx b/apps/next/src/pages/index.tsx index 33039b4d..2a46120d 100644 --- a/apps/next/src/pages/index.tsx +++ b/apps/next/src/pages/index.tsx @@ -11,18 +11,14 @@ export default function Home(): JSX.Element { const dotlottie = new Dotlottiejs(); await dotlottie - .setAuthor('Joe') - .setVersion('1.0') .addAnimation({ id: 'animation_1', // eslint-disable-next-line no-secrets/no-secrets url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - autoplay: true, }) .addAnimation({ id: 'animation_2', url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, }) .build() .then((value) => { diff --git a/apps/next/src/pages/ssProps.tsx b/apps/next/src/pages/ssProps.tsx index ca70f066..1b94be63 100644 --- a/apps/next/src/pages/ssProps.tsx +++ b/apps/next/src/pages/ssProps.tsx @@ -2,7 +2,7 @@ * Copyright 2023 Design Barn Inc. */ -import { DotLottie } from '@dotlottie/dotlottie-js/node'; +import { DotLottie } from '@dotlottie/dotlottie-js'; import styles from '@/styles/Home.module.css'; @@ -18,18 +18,14 @@ export async function getServerSideProps(): Promise<{ const dotlottie = new DotLottie(); await dotlottie - .setAuthor('Joe') - .setVersion('1.0') .addAnimation({ id: 'animation_1', // eslint-disable-next-line no-secrets/no-secrets url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - autoplay: true, }) .addAnimation({ id: 'animation_2', url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, }) .build(); diff --git a/apps/node/audio_test.mjs b/apps/node/audio_test.mjs index f737f92d..65c9d74c 100644 --- a/apps/node/audio_test.mjs +++ b/apps/node/audio_test.mjs @@ -18,8 +18,6 @@ import { getAnimation, getAudio, getAllAudio } from '@dotlottie/dotlottie-js/nod // const dl = await // dotLottie -// .setAuthor('Sam!') -// .setVersion('1.0') // .addAnimation({ // id: 'audio', // // url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json', @@ -64,8 +62,6 @@ import { getAnimation, getAudio, getAllAudio } from '@dotlottie/dotlottie-js/nod const double = new DotLottie(); const doubleAnimation = await double - .setAuthor('Sam!') - .setVersion('1.0') .addAnimation({ id: 'audio_0', // url: 'https://assets10.lottiefiles.com/packages/lf20_tykuirhr.json', diff --git a/apps/node/lf-interactivity-page-generator.mjs b/apps/node/lf-interactivity-page-generator.mjs index 28ca0ef2..206f7062 100644 --- a/apps/node/lf-interactivity-page-generator.mjs +++ b/apps/node/lf-interactivity-page-generator.mjs @@ -28,8 +28,6 @@ async function createDotLottie() { // Can't change to a different animation // TODO: MACROS!! START_FRAME / END_FRAME const dl = await dotLottie - .setAuthor('Sam!') - .setVersion('1.0') .addAnimation({ id: 'segments', url: 'https://assets2.lottiefiles.com/packages/lf20_4fET62.json', @@ -608,8 +606,6 @@ async function createSingles() { const stateSegments = new DotLottie(); await stateSegments - .setAuthor('Sam!') - .setVersion('1.0') .addAnimation({ id: 'segments', url: 'https://assets2.lottiefiles.com/packages/lf20_4fET62.json', diff --git a/apps/node/package.json b/apps/node/package.json index c48e0e38..2b2ba203 100644 --- a/apps/node/package.json +++ b/apps/node/package.json @@ -1,5 +1,6 @@ { - "name": "node", + "name": "node-example", + "private": true, "version": "1.0.0", "description": "", "author": "", @@ -8,6 +9,6 @@ "keywords": [], "scripts": {}, "dependencies": { - "@dotlottie/dotlottie-js": "workspace:^" + "@dotlottie/dotlottie-js": "workspace:*" } } diff --git a/apps/node/script.mjs b/apps/node/script.mjs deleted file mode 100644 index 1a221789..00000000 --- a/apps/node/script.mjs +++ /dev/null @@ -1,1248 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -import fs from 'fs'; - -import { DotLottie } from '@dotlottie/dotlottie-js/node'; - -async function createDotLottieForTests() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: 'pigeon_fsm', - initial: 0, - }, - states: [ - { - name: "pigeon", - animation_id: "pigeon", - type: "PlaybackState", - autoplay: true, - loop: false, - marker: "bird" - }, - { - name: "explosion", - animation_id: "pigeon", - type: "PlaybackState", - autoplay: true, - speed: 0.8, - loop: false, - marker: 'explosion', - }, - { - name: "feathers", - animation_id: "pigeon", - type: "PlaybackState", - autoplay: true, - speed: 0.8, - loop: false, - marker: 'feathers', - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - on_complete_event: {}, - }, - ], - context_variables: [], - listeners: [] - }) - .addStateMachine({ - descriptor: { - id: 'pigeon_without_explosion', - initial: 0, - }, - states: [ - { - name: "pigeon", - animation_id: "pigeon", - type: "PlaybackState", - autoplay: true, - loop: false, - marker: "bird" - }, - { - name: "feathers", - animation_id: "pigeon", - type: "PlaybackState", - autoplay: true, - speed: 0.8, - loop: false, - marker: 'feathers', - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 1, - to_state: 0, - on_complete_event: {}, - }, - ], - context_variables: [], - listeners: [] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('exploding-pigeons-test-file.lottie', Buffer.from(value)); - }); -} - -async function createExplodingPigeon() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Sam') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: "explodingPigeon", - initial: "pigeonRunning" - }, - states: [ - { - type: "PlaybackState", - name: "pigeonRunning", - animationId: "", - loop: true, - autoplay: true, - segment: "bird", - transitions: [ - { - type: "Transition", - toState: "explosion", - guards: [ - { - type: "Event", - triggerName: "explode" - } - ] - } - ] - }, - { - type: "PlaybackState", - name: "explosion", - animationId: "", - loop: false, - autoplay: true, - segment: "explosion", - transitions: [ - { - type: "Transition", - toState: "feathersFalling", - guards: [ - { - type: "Event", - triggerName: "rainFeathers" - } - ] - } - ] - }, - { - type: "PlaybackState", - name: "feathersFalling", - animationId: "", - loop: false, - autoplay: true, - segment: "feathers", - transitions: [ - { - type: "Transition", - toState: "pigeonRunning", - guards: [ - { - type: "Event", - triggerName: "restart" - } - ] - } - ] - } - ], - listeners: [ - { - type: "PointerDown", - actions: [ - { - type: "Fire", - triggerName: "explode" - } - ] - }, - { - type: "OnComplete", - stateName: "explosion", - actions: [ - { - type: "Fire", - triggerName: "rainFeathers" - } - ] - }, - { - type: "PointerDown", - actions: [ - { - type: "Fire", - triggerName: "restart" - } - ] - } - ], - triggers: [ - { - type: "Event", - name: "explode" - }, - { - type: "Event", - name: "rainFeathers" - }, - { - type: "Event", - name: "restart" - } - ] - }) - .addStateMachine({ - descriptor: { - id: "pigeonWithoutExplosion", - initial: "pigeonRunning" - }, - states: [ - { - type: "PlaybackState", - name: "pigeonRunning", - animationId: "", - loop: true, - autoplay: true, - segment: "bird", - transitions: [ - { - type: "Transition", - toState: "feathersFalling", - guards: [ - { - type: "Event", - triggerName: "explode" - } - ] - } - ] - }, - { - type: "PlaybackState", - name: "feathersFalling", - animationId: "", - loop: false, - autoplay: true, - segment: "feathers", - transitions: [ - { - type: "Transition", - toState: "pigeonRunning", - guards: [ - { - type: "Event", - triggerName: "restart" - } - ] - } - ] - } - ], - listeners: [ - { - type: "PointerDown", - actions: [ - { - type: "Fire", - triggerName: "explode" - } - ] - }, - { - type: "PointerDown", - actions: [ - { - type: "Fire", - triggerName: "restart" - } - ] - } - ], - triggers: [ - { - type: "Event", - name: "explode" - }, - { - type: "Event", - name: "rainFeathers" - }, - { - type: "Event", - name: "restart" - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('exploding_pigeon.lottie', Buffer.from(value)); - }); -} - -async function createListenersAnimation() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: 'pigeon_fsm', - initial: 0, - }, - states: [ - { - name: "pigeon", - type: "PlaybackState", - mode: "Forward", - speed: 1, - use_frame_interpolation: true, - autoplay: true, - loop: true, - marker: "bird" - }, - { - name: "explosion", - type: "PlaybackState", - mode: "Forward", - autoplay: true, - speed: 0.5, - loop: false, - marker: 'explosion', - }, - { - name: "feathers", - type: "PlaybackState", - autoplay: true, - speed: 1, - loop: false, - marker: 'feathers', - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - on_pointer_down_event: {}, - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - on_complete_event: {}, - }, - ], - listeners: [ - { - type: "PointerUp", - target: "button_0", - action: "set", - value: 1, - context_key: "counter_0" - }, - { - type: "PointerDown" - }, - { - type: "PointerEnter" - }, - { - type: "PointerExit" - }, - { - type: "PointerMove" - }, - ], - context_variables: [ - { - type: "Numeric", - key: "counter", - value: "0" - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('pigeon_with_listeners.lottie', Buffer.from(value)); - }); -} - -async function createDotLottie() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'animation_1', - url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - }) - .addAnimation({ - id: 'animation_2', - url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, - }) - .addStateMachine({ - id: 'state_1', - state: { - descriptor: { - id: 'state_1', - animationId: 'animation_1', - }, - states: { - runState: { - statePlaybackSettings: { - autoplay: true, - loop: 3, - direction: 1, - segments: 'explosion', - }, - onComplete: { - state: 'feathers', - }, - }, - }, - }, - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('test_from_node.lottie', Buffer.from(value)); - }); -} - -async function create_pigeon_fsm_eq_guard() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: "fsm_eq_guard", - initial: 0 - }, - states: [ - { - name: "pigeon", - type: "PlaybackState", - loop: true, - autoplay: true, - marker: "bird", - use_frame_interpolation: true, - }, - { - name: "explosion", - type: "PlaybackState", - loop: false, - autoplay: true, - speed: 0.5, - marker: "explosion", - use_frame_interpolation: true, - }, - { - name: "feather", - type: "PlaybackState", - loop: false, - autoplay: true, - marker: "feather", - use_frame_interpolation: true, - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - string_event: { - value: "explosion" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "Equal", - compare_to: 5.0 - } - ] - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - string_event: { - value: "complete" - }, - guards: [ - { - type: "String", - context_key: "counter_1", - condition_type: "Equal", - compare_to: "to_be_the_same" - } - ] - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - string_event: { - value: "done" - }, - guards: [ - { - type: "Boolean", - context_key: "counter_2", - condition_type: "Equal", - compare_to: true - } - ] - } - ], - listeners: [ - { - type: "PointerDown" - } - ], - context_variables: [ - { - type: "Numeric", - key: "counter_0", - value: 1 - }, - { - type: "String", - key: "counter_1", - value: "init" - }, - { - type: "Boolean", - key: "counter_2", - value: false - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('pigeon_fsm_eq_guard.lottie', Buffer.from(value)); - }); -} - -async function create_pigeon_gt_gte_guard() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: "gt_gte_guard", - initial: 0 - }, - states: [ - { - name: "pigeon", - type: "PlaybackState", - loop: true, - autoplay: true, - marker: "bird", - use_frame_interpolation: true, - }, - { - name: "explosion", - type: "PlaybackState", - loop: false, - autoplay: true, - speed: 0.5, - marker: "explosion", - use_frame_interpolation: true, - }, - { - name: "feather", - type: "PlaybackState", - loop: false, - autoplay: true, - marker: "feather", - use_frame_interpolation: true, - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - string_event: { - value: "explosion" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "GreaterThan", - compare_to: 5.0 - } - ] - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - string_event: { - value: "complete" - }, - guards: [ - { - type: "String", - context_key: "counter_0", - condition_type: "GreaterThanOrEqual", - compare_to: 60.0 - } - ] - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - string_event: { - value: "done" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "GreaterThanOrEqual", - compare_to: 65.0 - } - ] - } - ], - listeners: [ - { - type: "PointerDown" - } - ], - context_variables: [ - { - type: "Numeric", - key: "counter_0", - value: 1 - }, - { - type: "String", - key: "STRING_GUARD", - value: "SUPER_SECRET_VALUE" - }, - { - type: "Boolean", - key: "counter_2", - value: false - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('pigeon_fsm_gt_gte_guard.lottie', Buffer.from(value)); - }); -} - -async function create_pigeon_lt_lte_guard() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: "lt_lte_guard", - initial: 0 - }, - states: [ - { - name: "pigeon", - type: "PlaybackState", - loop: true, - autoplay: true, - mode: "Forward", - marker: "bird", - use_frame_interpolation: true, - }, - { - name: "explosion", - type: "PlaybackState", - loop: false, - autoplay: true, - speed: 0.5, - marker: "explosion", - use_frame_interpolation: true, - }, - { - name: "feather", - type: "PlaybackState", - loop: false, - autoplay: true, - marker: "feather", - use_frame_interpolation: true, - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - string_event: { - value: "explosion" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "LessThan", - compare_to: 5.0 - } - ] - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - string_event: { - value: "complete" - }, - guards: [ - { - type: "String", - context_key: "counter_0", - condition_type: "LessThanOrEqual", - compare_to: 10.0 - } - ] - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - string_event: { - value: "done" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "LessThanOrEqual", - compare_to: 15.0 - } - ] - } - ], - listeners: [ - { - type: "PointerDown" - } - ], - context_variables: [ - { - type: "Numeric", - key: "counter_0", - value: 1 - }, - { - type: "String", - key: "STRING_GUARD", - value: "SUPER_SECRET_VALUE" - }, - { - type: "Boolean", - key: "counter_2", - value: false - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('pigeon_fsm_lt_lte_guard.lottie', Buffer.from(value)); - }); -} - -async function create_pigeon_ne_guard() { - const dotLottie = new DotLottie(); - - await dotLottie - .setAuthor('Joe') - .setVersion('1.0') - .addAnimation({ - id: 'pigeon', - url: 'https://lottie.host/071a2de9-52ca-4ce4-ba2f-a5befd220bdd/ECzVp4eaMa.json', - }) - .addStateMachine({ - descriptor: { - id: "ne_guard", - initial: 0 - }, - states: [ - { - name: "pigeon", - type: "PlaybackState", - loop: true, - autoplay: true, - marker: "bird", - use_frame_interpolation: true, - }, - { - name: "explosion", - type: "PlaybackState", - loop: false, - autoplay: true, - speed: 0.5, - marker: "explosion", - use_frame_interpolation: true, - }, - { - name: "feather", - type: "PlaybackState", - loop: false, - autoplay: true, - marker: "feather", - use_frame_interpolation: true, - entry_actions: [], - exit_actions: [] - } - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - string_event: { - value: "explosion" - }, - guards: [ - { - type: "Numeric", - context_key: "counter_0", - condition_type: "NotEqual", - compare_to: 5.0 - } - ] - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - string_event: { - value: "complete" - }, - guards: [ - { - type: "String", - context_key: "counter_1", - condition_type: "NotEqual", - compare_to: "to_be_the_same" - } - ] - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - string_event: { - value: "done" - }, - guards: [ - { - type: "Boolean", - context_key: "counter_2", - condition_type: "NotEqual", - compare_to: true - } - ] - } - ], - listeners: [ - { - type: "PointerDown" - } - ], - context_variables: [ - { - type: "Numeric", - key: "counter_0", - value: 1 - }, - { - type: "String", - key: "counter_1", - value: "init" - }, - { - type: "Boolean", - key: "counter_2", - value: false - } - ] - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('pigeon_fsm_ne_guard.lottie', Buffer.from(value)); - }); -} - -async function create_toggle_button() { - const toggle = new DotLottie(); - - await toggle - .addAnimation({ - id: 'toggle', - url: 'https://assets8.lottiefiles.com/private_files/lf30_tnblylie.json', - }) - .addStateMachine({ - // Define the id of the state machine and the initial state - descriptor: { - id: 'state_toggle', - initial: 0, - }, - states: [ - { - name: 'wait', - type: 'PlaybackState', - autoplay: false, - loop: false, - segment: [0, 1], - }, - { - name: 'play_forward', - type: 'PlaybackState', - autoplay: true, - loop: false, - segment: [0, 30], - }, - { - name: 'play_reverse', - type: 'PlaybackState', - autoplay: true, - loop: false, - mode: "Reverse", - segment: [30, 0], - }, - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - on_pointer_down_event: {}, - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - on_pointer_down_event: {}, - }, - { - type: "Transition", - from_state: 2, - to_state: 1, - on_pointer_down_event: {}, - }, - ], - listeners: [ - { - type: 'PointerDown', - }, - ], - context_variables: [], - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('toggle.lottie', Buffer.from(value)); - }); -} - -async function create_sync_animation() { - const toggle = new DotLottie(); - - await toggle - .addAnimation({ - id: 'toggle', - url: 'https://assets5.lottiefiles.com/private_files/lf30_hhvn1H.json', - }) - .addStateMachine({ - descriptor: { - id: 'sync_to_scroll', - initial: 0, - }, - states: [ - { - name: 'sync_to_scroll', - type: 'SyncState', - frame_context_key: "frame" - }, - ], - transitions: [ - ], - listeners: [ - ], - context_variables: [{ - type: "Numeric", - key: "frame", - value: 0 - }], - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('sync_to_scroll.lottie', Buffer.from(value)); - }); -} - -async function create_showcase() { - const showcase = new DotLottie(); - - await showcase.addAnimation({ - id: 'firstAnimation', - url: 'https://lottie.host/7047918e-2ba5-4bf1-a552-3349f19ef789/Q1yB2lgufS.json', - }).addAnimation({ - id: 'secondAnimation', - url: 'https://lottie.host/b658c6c9-77dc-4ecf-8519-43a3dc02a36e/tK4tGqJh2u.json' - }).addAnimation({ - id: 'thirdAnimation', - url: 'https://lottie.host/e8c3091c-4f51-49b8-91e3-1086ca3b8d00/JWm1lQEuZW.json' - }).addStateMachine({ - descriptor: { - id: 'showcaseMachine', - initial: 0 - }, - states: [ - { - name: "first_animation", - animation_id: "firstAnimation", - type: "PlaybackState", - autoplay: true, - loop: false, - }, - { - name: "second_animation", - animation_id: "secondAnimation", - type: "PlaybackState", - autoplay: true, - loop: false, - }, - { - name: "third_animation", - animation_id: "thirdAnimation", - type: "PlaybackState", - autoplay: true, - loop: false, - }, - ], - transitions: [ - { - type: "Transition", - from_state: 0, - to_state: 1, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 1, - to_state: 2, - on_complete_event: {}, - }, - { - type: "Transition", - from_state: 2, - to_state: 0, - on_complete_event: {}, - }, - ], - listeners: [], - context_variables: [] - }) - - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('showcase.lottie', Buffer.from(value)); - }); -} - -async function logImages(arrayBuffer) { - // Create a new DotLottie instance from the array buffer - const dotLottie = await new DotLottie().fromArrayBuffer(arrayBuffer); - - // Build the dotLottie instance - await dotLottie.build(); - - // Retrieve and log the image assets' IDs - const images = dotLottie.getImages().map((image) => image.id); - - console.log({ - images, - }); - - // Return the instance for further operations - return dotLottie; -} - -async function debugScenario1() { - const dotLottie = new DotLottie(); - - // Fetch the dotLottie file and convert it to an ArrayBuffer - const res = await fetch('https://lottie.host/7c65acc7-80e7-4bad-8098-6cebd17c8b92/ee8uf2TX6N.lottie'); - const arrayBuffer = await res.arrayBuffer(); - - // Log the image assets on the first import - const dotLottie1 = await logImages(arrayBuffer); - - // Rebuild the dotLottie instance (simulating re-import) - await dotLottie1.build(); - - // Convert the instance back to an ArrayBuffer - const buffer = await dotLottie1.toArrayBuffer({}); - - // Log the image assets after the second import - await logImages(buffer); -} - -async function debugScenario2() { - const dotLottie = new DotLottie(); - - await dotLottie.addAnimation({ - id: 'bull', - url: 'https://lottie.host/c7d4e60a-102a-4ca1-97f1-f73b2b5f0f7b/s2z6VrPpnn.json', - }).addAnimation({ - id: 'shrek', - url: 'https://lottie.host/204e1620-32b7-46a1-8f99-eda64771b781/hYeiO04uqx.json' - }) - .build() - .then((value) => { - return value.toArrayBuffer(); - }) - .then((value) => { - fs.writeFileSync('new-issue.lottie', Buffer.from(value)); - }); - -} - -// create_showcase(); - -debugScenario1(); -// debugScenario2(); - -// createExplodingPigeon(); -// createListenersAnimation(); -// create_pigeon_fsm_eq_guard(); -// create_pigeon_gt_gte_guard(); -// create_pigeon_lt_lte_guard(); -// create_pigeon_ne_guard(); - - -/** Sun moon toggle button */ -// create_toggle_button(); - -/** Coffee drinker sync to scroll. Recreate the animation on the Interactivity homepage */ -/* lottiefiles.com/interactivity */ -// create_sync_animation(); - diff --git a/apps/node/state_machines.mjs b/apps/node/state_machines.mjs new file mode 100644 index 00000000..ab02ef58 --- /dev/null +++ b/apps/node/state_machines.mjs @@ -0,0 +1,1051 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import fs from 'fs'; + +import { DotLottie } from '@dotlottie/dotlottie-js'; + +async function createStarRating() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'star-rating', + url: 'https://lottie.host/90e7f6e9-462a-4560-b1d7-4cedf81b5421/bWWD1bAgE4.json', + }) + .addStateMachine({ + id: "starRating", + name: "Star Rating ⭐", + data: { + initial: "global", + states: [ + { + name: "global", + type: "GlobalState", + animation: "", + transitions: [ + { + type: "Transition", + toState: "star_1", + guards: [ + { + type: "Numeric", + conditionType: "Equal", + triggerName: "rating", + compareTo: 1 + } + ] + }, + { + type: "Transition", + toState: "star_2", + guards: [ + { + type: "Numeric", + conditionType: "Equal", + triggerName: "rating", + compareTo: 2 + } + ] + }, + { + type: "Transition", + toState: "star_3", + guards: [ + { + type: "Numeric", + conditionType: "Equal", + triggerName: "rating", + compareTo: 3 + } + ] + }, + { + type: "Transition", + toState: "star_4", + guards: [ + { + type: "Numeric", + conditionType: "Equal", + triggerName: "rating", + compareTo: 4 + } + ] + }, + { + type: "Transition", + toState: "star_5", + guards: [ + { + type: "Numeric", + conditionType: "Equal", + triggerName: "rating", + compareTo: 5 + } + ] + } + ] + }, + { + type: "PlaybackState", + name: "star_1", + animation: "", + autoplay: true, + segment: "star_1", + transitions: [] + }, + { + type: "PlaybackState", + name: "star_2", + animation: "", + autoplay: true, + segment: "star_2", + transitions: [] + }, + { + type: "PlaybackState", + name: "star_3", + animation: "", + autoplay: true, + segment: "star_3", + transitions: [] + }, + { + type: "PlaybackState", + name: "star_4", + animation: "", + autoplay: true, + segment: "star_4", + transitions: [] + }, + { + type: "PlaybackState", + name: "star_5", + animation: "", + autoplay: true, + segment: "star_5", + transitions: [] + } + ], + listeners: [ + { + type: "PointerDown", + layerName: "star1", + actions: [ + { + type: "SetNumeric", + triggerName: "rating", + value: 1 + } + ] + }, + { + type: "PointerDown", + layerName: "star2", + actions: [ + { + type: "SetNumeric", + triggerName: "rating", + value: 2 + } + ] + }, + { + type: "PointerDown", + layerName: "star3", + actions: [ + { + type: "SetNumeric", + triggerName: "rating", + value: 3 + } + ] + }, + { + type: "PointerDown", + layerName: "star4", + actions: [ + { + type: "SetNumeric", + triggerName: "rating", + value: 4 + } + ] + }, + { + type: "PointerDown", + layerName: "star5", + actions: [ + { + type: "SetNumeric", + triggerName: "rating", + value: 5 + } + ] + } + ], + triggers: [ + { + type: "Numeric", + name: "rating", + value: 0 + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_star_rating.lottie', Buffer.from(value)); + }); +} + +async function createSyncToCursor() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'sync-to-cursor', + url: 'https://lottie.host/a72900c7-2bb0-49d4-a8ae-58190f96b73d/PFTcmzsGhc.json', + }) + .addStateMachine({ + id: "syncToCursor", + name: "Sync to Cursor 🖱️", + data: { + initial: "Start", + states: [ + { + animation: "scroll", + type: "PlaybackState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Start", + guards: [ + { + type: "Event", + triggerName: "Step" + } + ] + } + ], + entryActions: [ + { + type: "SetFrame", + value: "$Progress" + } + ] + } + ], + listeners: [{ + type: "PointerDown", + actions: [ + { + type: "Increment", + triggerName: "Progress" + } + ] + }], + triggers: [ + { + type: "Numeric", + name: "Progress", + value: 0 + }, + { + type: "Event", + name: "Step" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_sync_to_cursor.lottie', Buffer.from(value)); + }); +} + +async function createHoverButton() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'hoverBtn', + url: 'https://lottie.host/9be40797-ef92-451f-add9-d6f66a78635a/lqnums50Xh.json', + }) + .addStateMachine({ + id: "hoverButton", + name: "Hover Button 🖱️", + data: { + initial: "Start", + states: [ + { + animation: "", + type: "GlobalState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Forward", + guards: [ + { + type: "Event", + triggerName: "Forward" + } + ] + }, + { + type: "Transition", + toState: "Reverse", + guards: [ + { + type: "Event", + triggerName: "Reverse" + } + ] + } + ] + }, + { + animation: "", + type: "PlaybackState", + name: "Forward", + mode: "Forward", + autoplay: true, + transitions: [] + }, + { + animation: "", + type: "PlaybackState", + name: "Reverse", + mode: "Reverse", + autoplay: true, + transitions: [] + } + ], + listeners: [ + { + type: "PointerEnter", + actions: [ + { + type: "Fire", + triggerName: "Forward" + } + ] + }, + { + type: "PointerExit", + actions: [ + { + type: "Fire", + triggerName: "Reverse" + } + ] + } + ], + triggers: [ + { + type: "Event", + name: "Forward" + }, + { + type: "Event", + name: "Reverse" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_hover_button.lottie', Buffer.from(value)); + }); +} + +async function createToggleButton() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'toggleBtn', + url: 'https://lottie.host/8c2590c3-3aaa-4d47-b6cd-1ef979e284f4/2ykhbXYDAc.json', + }) + .addStateMachine({ + id: "toggleButton", + name: "Toggle Button 🔄", + data: { + initial: "initial-wait", + states: [ + { + name: "initial-wait", + type: "PlaybackState", + animation: "", + transitions: [ + { + type: "Transition", + toState: "a", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: true + } + ] + } + ] + }, + { + name: "a", + type: "PlaybackState", + animation: "", + autoplay: true, + speed: 2.0, + transitions: [ + { + type: "Transition", + toState: "b", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: false + } + ] + } + ] + }, + { + name: "b", + type: "PlaybackState", + animation: "", + autoplay: true, + speed: 2.0, + mode: "Reverse", + transitions: [ + { + type: "Transition", + toState: "a", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: true + } + ] + } + ] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Toggle", + triggerName: "OnOffSwitch" + } + ] + } + ], + triggers: [ + { + type: "Boolean", + name: "OnOffSwitch", + value: false + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_toggle_button.lottie', Buffer.from(value)); + }); +} + +async function createThemeAction() { + const dotLottie = new DotLottie(); + + dotLottie.fromURL('https://lottie.host/9a5a6605-fc90-4935-8d10-9df4c83902ff/PFUKH53LJk.lottie').then((value) => { + value.addStateMachine({ + id: "themeAction", + name: "Theme Action 🎨", + data: { + initial: "initial-wait", + states: [ + { + name: "initial-wait", + type: "PlaybackState", + animation: "", + transitions: [ + { + type: "Transition", + toState: "a", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: true + } + ] + } + ], + entryActions: [ + { + type: "SetTheme", + value: "air" + } + ] + }, + { + name: "a", + type: "PlaybackState", + animation: "", + autoplay: true, + speed: 2.0, + transitions: [ + { + type: "Transition", + toState: "b", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: false + } + ] + } + ], + entryActions: [ + { + type: "SetTheme", + value: "Water" + } + ] + }, + { + name: "b", + type: "PlaybackState", + animation: "", + autoplay: true, + speed: 2.0, + mode: "Reverse", + transitions: [ + { + type: "Transition", + toState: "a", + guards: [ + { + type: "Boolean", + conditionType: "Equal", + triggerName: "OnOffSwitch", + compareTo: true + } + ] + } + ], + entryActions: [ + { + type: "SetTheme", + value: "earth" + } + ] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Toggle", + triggerName: "OnOffSwitch" + } + ] + } + ], + triggers: [ + { + type: "Boolean", + name: "OnOffSwitch", + value: false + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_theme_action.lottie', Buffer.from(value)); + }); + }); +} + +async function createExplodingPigeon() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'exploding-pigeon', + url: 'https://lottie.host/899f8fbb-55db-4a51-8a64-96f1bc4e45d7/9BiY1S0tDm.json', + }) + .addStateMachine({ + id: "explodingPigeon", + name: "Exploding Pigeon 💥", + data: { + initial: "Pigeon Running", + states: [ + { + animation: "pigeon", + type: "PlaybackState", + name: "Pigeon Running", + loop: true, + autoplay: true, + segment: "bird", + transitions: [ + { + type: "Transition", + toState: "Explosion", + guards: [ + { + type: "Event", + triggerName: "Explode" + } + ] + } + ] + }, + { + animation: "pigeon", + type: "PlaybackState", + name: "Explosion", + loop: false, + autoplay: true, + segment: "explosion", + speed: 0.1, + transitions: [ + { + type: "Transition", + toState: "Feathers falling", + guards: [ + { + type: "Event", + triggerName: "Rain feathers" + } + ] + } + ] + }, + { + animation: "pigeon", + type: "PlaybackState", + name: "Feathers falling", + loop: false, + autoplay: true, + segment: "feather", + transitions: [ + { + type: "Transition", + toState: "Pigeon Running", + guards: [ + { + type: "Event", + triggerName: "Restart" + } + ] + } + ] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Fire", + triggerName: "Explode" + } + ] + }, + { + type: "OnComplete", + stateName: "Explosion", + actions: [ + { + type: "Fire", + triggerName: "Rain feathers" + } + ] + }, + { + type: "OnComplete", + stateName: "Feathers falling", + actions: [ + { + type: "Fire", + triggerName: "Restart" + } + ] + } + ], + triggers: [ + { + type: "Event", + name: "Explode" + }, + { + type: "Event", + name: "Rain feathers" + }, + { + type: "Event", + name: "Restart" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_exploding_pigeon.lottie', Buffer.from(value)); + }); +} + +async function createHoldButton() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'holdBtn', + url: 'https://lottie.host/9be40797-ef92-451f-add9-d6f66a78635a/lqnums50Xh.json', + }) + .addStateMachine({ + id: "holdButton", + name: "Hold Button 🖱️", + data: { + initial: "Start", + states: [ + { + animation: "", + type: "GlobalState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Forward", + guards: [ + { + type: "Event", + triggerName: "Forward" + } + ] + }, + { + type: "Transition", + toState: "Reverse", + guards: [ + { + type: "Event", + triggerName: "Reverse" + } + ] + } + ] + }, + { + animation: "", + type: "PlaybackState", + name: "Forward", + mode: "Forward", + autoplay: true, + transitions: [] + }, + { + animation: "", + type: "PlaybackState", + name: "Reverse", + mode: "Reverse", + autoplay: true, + transitions: [] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Fire", + triggerName: "Forward" + } + ] + }, + { + type: "PointerUp", + actions: [ + { + type: "Fire", + triggerName: "Reverse" + } + ] + } + ], + triggers: [ + { + type: "Event", + name: "Forward" + }, + { + type: "Event", + name: "Reverse" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_hold_button.lottie', Buffer.from(value)); + }); +} + +async function createClickButton() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'clickBtn', + url: 'https://lottie.host/9be40797-ef92-451f-add9-d6f66a78635a/lqnums50Xh.json', + }) + .addStateMachine({ + id: "clickButton", + name: "Click Button 🖱️", + data: { + initial: "Start", + states: [ + { + animation: "", + type: "GlobalState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Forward", + guards: [ + { + type: "Event", + triggerName: "Forward" + } + ] + } + ] + }, + { + animation: "", + type: "PlaybackState", + name: "Forward", + mode: "Forward", + autoplay: true, + transitions: [] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Fire", + triggerName: "Forward" + } + ] + } + ], + triggers: [ + { + type: "Event", + name: "Forward" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_click_button.lottie', Buffer.from(value)); + }); +} + +async function createInteractiveStats() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'interactive-stats', + url: 'https://lottie.host/fba88936-b753-4751-a6ca-94db246157cf/5pGajCeC0B.json', + }) + .addStateMachine({ + id: "interactiveStats", + name: "Interactive Statistics 📊", + data: { + initial: "Start", + states: [ + { + animation: "stats", + type: "PlaybackState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Start", + guards: [ + { + type: "Event", + triggerName: "Step" + } + ] + } + ], + entryActions: [ + { + type: "SetProgress", + value: "$Progress" + } + ] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Increment", + triggerName: "Progress" + } + ] + } + ], + triggers: [ + { + type: "Numeric", + name: "Progress", + value: 0 + }, + { + type: "Event", + name: "Step" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_interactive_stats.lottie', Buffer.from(value)); + }); +} + +async function createLoader() { + const dotLottie = new DotLottie(); + + await dotLottie + .addAnimation({ + id: 'loader', + url: 'https://lottie.host/8bec049d-6b29-48f2-9592-df21853df42e/5AqIkEhG0r.json', + }) + .addStateMachine({ + id: "loader", + name: "Pretty Loader ⌛", + data: { + initial: "Start", + states: [ + { + animation: "stats", + type: "PlaybackState", + name: "Start", + transitions: [ + { + type: "Transition", + toState: "Start", + guards: [ + { + type: "Event", + triggerName: "Step" + } + ] + } + ], + entryActions: [ + { + type: "SetProgress", + value: "$Progress" + } + ] + } + ], + listeners: [ + { + type: "PointerDown", + actions: [ + { + type: "Increment", + triggerName: "Progress" + } + ] + } + ], + triggers: [ + { + type: "Numeric", + name: "Progress", + value: 0 + }, + { + type: "Event", + name: "Step" + } + ] + } + }) + .build() + .then((value) => { + return value.toArrayBuffer(); + }) + .then((value) => { + fs.writeFileSync('sm_loader.lottie', Buffer.from(value)); + }); +} + +// createStarRating(); +// createSyncToCursor(); +// createHoverButton(); +// createToggleButton(); +createThemeAction(); +// createExplodingPigeon(); +// createHoldButton(); +// createClickButton(); +// createInteractiveStats(); +// createLoader(); \ No newline at end of file diff --git a/apps/nuxt/.gitignore b/apps/nuxt/.gitignore deleted file mode 100644 index 90e126da..00000000 --- a/apps/nuxt/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -node_modules -*.log* -.nuxt -.nitro -.cache -.output -.env -dist -.DS_Store diff --git a/apps/nuxt/.npmrc b/apps/nuxt/.npmrc deleted file mode 100644 index c483022c..00000000 --- a/apps/nuxt/.npmrc +++ /dev/null @@ -1 +0,0 @@ -shamefully-hoist=true \ No newline at end of file diff --git a/apps/nuxt/README.md b/apps/nuxt/README.md deleted file mode 100644 index fa00c266..00000000 --- a/apps/nuxt/README.md +++ /dev/null @@ -1,42 +0,0 @@ -# Nuxt 3 Minimal Starter - -Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. - -## Setup - -Make sure to install the dependencies: - -```bash -# yarn -yarn install - -# npm -npm install - -# pnpm -pnpm install -``` - -## Development Server - -Start the development server on `http://localhost:3000` - -```bash -npm run dev -``` - -## Production - -Build the application for production: - -```bash -npm run build -``` - -Locally preview production build: - -```bash -npm run preview -``` - -Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. diff --git a/apps/nuxt/app.vue b/apps/nuxt/app.vue deleted file mode 100644 index 41729c4e..00000000 --- a/apps/nuxt/app.vue +++ /dev/null @@ -1,39 +0,0 @@ - - - diff --git a/apps/nuxt/nuxt.config.ts b/apps/nuxt/nuxt.config.ts deleted file mode 100644 index f2602b68..00000000 --- a/apps/nuxt/nuxt.config.ts +++ /dev/null @@ -1,2 +0,0 @@ -// https://nuxt.com/docs/api/configuration/nuxt-config -export default defineNuxtConfig({}); diff --git a/apps/nuxt/package.json b/apps/nuxt/package.json deleted file mode 100644 index c4442130..00000000 --- a/apps/nuxt/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "nuxt-app", - "private": true, - "scripts": { - "build": "nuxt build", - "dev": "nuxt dev", - "generate": "nuxt generate", - "postinstall": "nuxt prepare", - "preview": "nuxt preview" - }, - "dependencies": { - "@dotlottie/dotlottie-js": "workspace:^" - }, - "devDependencies": { - "@types/node": "^18", - "nuxt": "^3.4.2" - } -} diff --git a/apps/nuxt/public/favicon.ico b/apps/nuxt/public/favicon.ico deleted file mode 100644 index 18993ad91cfd43e03b074dd0b5cc3f37ab38e49c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmeHLOKuuL5PjK%MHWVi6lD zOGiREbCw`xmFozJ^aNatJY>w+g ze6a2@u~m#^BZm@8wco9#Crlli0uLb^3E$t2-WIc^#(?t)*@`UpuofJ(Uyh@F>b3Ph z$D^m8Xq~pTkGJ4Q`Q2)te3mgkWYZ^Ijq|hkiP^9`De={bQQ%heZC$QU2UpP(-tbl8 zPWD2abEew;oat@w`uP3J^YpsgT%~jT(Dk%oU}sa$7|n6hBjDj`+I;RX(>)%lm_7N{+B7Mu%H?422lE%MBJH!!YTN2oT7xr>>N-8OF$C&qU^ z>vLsa{$0X%q1fjOe3P1mCv#lN{xQ4_*HCSAZjTb1`}mlc+9rl8$B3OP%VT@mch_~G z7Y+4b{r>9e=M+7vSI;BgB?ryZDY4m>&wcHSn81VH1N~`0gvwH{ z8dv#hG|OK`>1;j7tM#B)Z7zDN?{6=dUal}$e - - - Player - - - - - - - - - -
-
- - - - - - - - -
- - - diff --git a/apps/react/package.json b/apps/react/package.json index a9d32714..fd7d9d20 100644 --- a/apps/react/package.json +++ b/apps/react/package.json @@ -1,5 +1,5 @@ { - "name": "react", + "name": "react-example", "version": "0.0.0", "type": "module", "private": true, @@ -17,7 +17,7 @@ "react-dom": "^18.2.0" }, "devDependencies": { - "@dotlottie/dotlottie-js": "workspace:^", + "@dotlottie/dotlottie-js": "workspace:*", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@typescript-eslint/eslint-plugin": "^5.57.1", diff --git a/apps/react/src/App.tsx b/apps/react/src/App.tsx index 06599f8e..51c9d1e1 100644 --- a/apps/react/src/App.tsx +++ b/apps/react/src/App.tsx @@ -22,17 +22,13 @@ function App() { const dotlottie = new DotLottie(); await dotlottie - .setAuthor('Joe') - .setVersion('1.0') .addAnimation({ id: 'animation_1', url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - autoplay: true, }) .addAnimation({ id: 'animation_2', url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, }) .build() .then((value) => { diff --git a/apps/vue/package.json b/apps/vue/package.json index d22aa909..419ffb4e 100644 --- a/apps/vue/package.json +++ b/apps/vue/package.json @@ -1,5 +1,5 @@ { - "name": "vue", + "name": "vue-example", "version": "0.0.0", "type": "module", "private": true, @@ -13,7 +13,7 @@ "vue": "^3.2.47" }, "devDependencies": { - "@dotlottie/dotlottie-js": "workspace:^", + "@dotlottie/dotlottie-js": "workspace:*", "@vitejs/plugin-vue": "^4.1.0", "typescript": "^5.6.3", "vite": "^4.3.0", diff --git a/apps/vue/src/components/HelloWorld.vue b/apps/vue/src/components/HelloWorld.vue index f813ecf8..37823012 100644 --- a/apps/vue/src/components/HelloWorld.vue +++ b/apps/vue/src/components/HelloWorld.vue @@ -15,17 +15,13 @@ onMounted(() => { const dotlottie = new DotLottie(); await dotlottie - .setAuthor('Joe') - .setVersion('1.0') .addAnimation({ id: 'animation_1', url: 'https://lottie.host/18b639d1-a200-4225-ba0e-3456d40f95a5/wlrsaqWa8r.json', - autoplay: true, }) .addAnimation({ id: 'animation_2', url: 'https://lottie.host/cf7b43d1-3d6b-407a-970b-6305b18bebfa/uB1Jboo1o1.json', - autoplay: true, }) .build() .then((value) => { diff --git a/package.json b/package.json index 938862a1..5c2b8e2d 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,6 @@ "homepage": "https://github.com/dotlottie/dotlottie-js#readme", "license": "MIT", "private": true, - "workspaces": [ - "packages/*" - ], "keywords": [ "dotLottie", "Lottie", @@ -41,7 +38,7 @@ "type-check": "turbo run type-check" }, "devDependencies": { - "@changesets/cli": "2.23.2", + "@changesets/cli": "2.27.9", "@commitlint/cli": "17.0.3", "@lottiefiles/commitlint-config": "2.0.0", "@lottiefiles/eslint-plugin": "3.0.0", @@ -64,5 +61,5 @@ "typescript": "4.7.4", "zx": "7.0.7" }, - "packageManager": "pnpm@8.8.0" + "packageManager": "pnpm@9.11.0" } diff --git a/packages/dotlottie-js/CHANGELOG.md b/packages/dotlottie-js/CHANGELOG.md index 91e7a7a7..17b006c6 100644 --- a/packages/dotlottie-js/CHANGELOG.md +++ b/packages/dotlottie-js/CHANGELOG.md @@ -1,5 +1,125 @@ # @dotlottie/dotlottie-js +## 0.9.0 + +### Minor Changes + +- 9457abf: feat: init dotLottie v2 +- c7749c3: refactor: remove setGenerator method from DotLottieCommonV1 and DotLottieCommon classes + +### Patch Changes + +- c7749c3: fix: dotLottie v1 <-> v2 conversion +- c7749c3: fix: dotLottie manifest version format +- c7749c3: small changes to state machine format. +- c7749c3: fix: export missing types for dotLottie v1 +- c7749c3: fix: update theme schema slotId +- c7749c3: refactor: update v2 manifest schema +- c7749c3: refactor: dotLottie conversion methods +- c7749c3: fix(conversion): 🐛 never build dotLottie instance before conversion +- c7749c3: fix: remove themes & stateMachines from manifest if non available +- f2a1db8: chore: bump lottie-types pkg to v1.2.0 +- c7749c3: refactor: add tests && update LottieThemeCommon toString method +- c7749c3: image asset ids +- c7749c3: small fixes +- c7749c3: fix: getAnimations & getImages & getAudios for v2 +- c7749c3: fix: add theme data schema +- c7749c3: fix: export missing types + +## 0.9.0-beta.14 + +### Minor Changes + +- b2aae20: refactor: remove setGenerator method from DotLottieCommonV1 and DotLottieCommon classes + +## 0.9.0-beta.13 + +### Patch Changes + +- c6fa979: refactor: add tests && update LottieThemeCommon toString method + +## 0.9.0-beta.12 + +### Patch Changes + +- dc0ce5d: fix: update theme schema slotId + +## 0.9.0-beta.11 + +### Patch Changes + +- b167892: refactor: update v2 manifest schema + +## 0.9.0-beta.10 + +### Patch Changes + +- 2134d08: fix(conversion): 🐛 never build dotLottie instance before conversion + +## 0.9.0-beta.9 + +### Patch Changes + +- 28e6b57: fix: getAnimations & getImages & getAudios for v2 + +## 0.9.0-beta.8 + +### Patch Changes + +- 926de78: fix: export missing types + +## 0.9.0-beta.7 + +### Patch Changes + +- 1611716: fix: add theme data schema + +## 0.9.0-beta.6 + +### Patch Changes + +- 74bfed1: fix: dotLottie manifest version format + +## 0.9.0-beta.5 + +### Patch Changes + +- df94db0: fix: remove themes & stateMachines from manifest if non available + +## 0.9.0-beta.4 + +### Patch Changes + +- 9a32fd4: fix: export missing types for dotLottie v1 + +## 0.9.0-beta.3 + +### Patch Changes + +- 81014cb: fix: dotLottie v1 <-> v2 conversion + +## 0.9.0-beta.2 + +### Patch Changes + +- 9e7fa8b: small fixes + +## 0.9.0-beta.1 + +### Patch Changes + +- 5570a8f: refactor: dotLottie conversion methods + +## 0.9.0-beta.0 + +### Minor Changes + +- 9457abf: feat: init dotLottie v2 + +### Patch Changes + +- f2a1db8: chore: bump lottie-types pkg to v1.2.0 + ## 0.8.1 ### Patch Changes diff --git a/packages/dotlottie-js/jasmine/jasmine-browser.json b/packages/dotlottie-js/jasmine/jasmine-browser.json deleted file mode 100644 index a096b335..00000000 --- a/packages/dotlottie-js/jasmine/jasmine-browser.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "srcDir": "src", - "specDir": "src/tests", - "specFiles": ["**/*.spec.mjs"], - "env": { - "stopSpecOnExpectationFailure": false, - "stopOnSpecFailure": false - }, - "browser": { - "name": "headlessChrome" - } -} diff --git a/packages/dotlottie-js/jasmine/jasmine.json b/packages/dotlottie-js/jasmine/jasmine.json deleted file mode 100644 index 9ff55de9..00000000 --- a/packages/dotlottie-js/jasmine/jasmine.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "env": { - "stopSpecOnExpectationFailure": true - }, - "spec_dir": "src/tests", - "spec_files": ["**/*.spec.mjs"] -} diff --git a/packages/dotlottie-js/jasmine/tsup.config.js b/packages/dotlottie-js/jasmine/tsup.config.js deleted file mode 100644 index 59be0d4e..00000000 --- a/packages/dotlottie-js/jasmine/tsup.config.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright 2022 Design Barn Inc. - */ - -import { defineConfig } from 'tsup'; - -export default defineConfig(({ platform }) => { - const config = { - clean: true, - entry: platform === 'browser' ? ['src/tests/**/*-browser.spec.ts'] : ['src/tests/**/*-node.spec.ts'], - format: ['esm'], - treeshake: true, - outDir: 'src/tests/dist', - platform, - target: ['esnext'], - tsconfig: 'tsconfig.build.json', - noExternal: - platform === 'browser' ? ['fflate', 'file-type', 'browser-image-hash', 'valibot'] : ['browser-image-hash'], - loader: { - '.lottie': 'binary', - }, - outExtension: ({ format }) => ({ - js: `.${format === 'esm' ? 'mjs' : format}`, - }), - }; - - return config; -}); diff --git a/packages/dotlottie-js/package.json b/packages/dotlottie-js/package.json index e318678a..d8c184fc 100644 --- a/packages/dotlottie-js/package.json +++ b/packages/dotlottie-js/package.json @@ -1,6 +1,6 @@ { "name": "@dotlottie/dotlottie-js", - "version": "0.8.1", + "version": "0.9.0", "type": "module", "description": "This library helps in creating and modifying .lottie files.", "repository": { @@ -21,16 +21,21 @@ "engines": { "node": ">=18.0.0" }, - "main": "./dist/index.js", + "main": "./dist/index.node.js", "exports": { - ".": "./dist/index.js", - "./node": "./dist/node/index.js" + ".": { + "node": "./dist/index.node.js", + "default": "./dist/index.browser.js" + }, + "./*": "./dist/*.js" }, - "types": "./dist/index.d.ts", + "browser": "dist/index.browser.js", + "types": "./dist/index.browser.d.ts", "typesVersions": { "*": { - "node": [ - "./dist/node" + "*": [ + "dist/*", + "dist/index.browser.d.ts" ] } }, @@ -44,14 +49,9 @@ "lint": "eslint --fix .", "stats:eslint": "cross-env TIMING=1 eslint .", "stats:ts": "tsc -p tsconfig.build.json --extendedDiagnostics", - "test": "pnpm test:browser && pnpm test:node", - "test:browser": "pnpm test:build:browser && jasmine-browser-runner runSpecs --config=./jasmine/jasmine-browser.json --port=4444", - "test:browser:watch": "nodemon -e ts --watch src/tests --exec 'pnpm test:browser'", - "test:build:browser": "tsup --platform='browser' --config ./jasmine/tsup.config.js", - "test:build:node": "tsup --platform='node' --config ./jasmine/tsup.config.js", - "test:build:watch": "pnpm test:build --watch", - "test:node": "pnpm test:build:node && jasmine --config=./jasmine/jasmine.json --parallel=auto", - "test:node:watch": "nodemon -e ts --watch src/tests --exec 'pnpm test:node'", + "test": "pnpm run test:node && pnpm run test:browser", + "test:browser": "vitest --config=vitest.browser.config.js", + "test:node": "vitest --config=vitest.config.js", "type-check": "tsc --noEmit" }, "dependencies": { @@ -65,20 +65,20 @@ }, "devDependencies": { "@types/jasmine": "4.3.5", - "@types/node": "18.0.6", + "@types/node": "22.8.7", "@types/sharp": "0.31.1", + "@vitest/browser": "2.1.3", + "@vitest/coverage-v8": "2.1.3", "cross-env": "7.0.3", - "esbuild": "0.14.49", - "jasmine": "5.1.0", - "jasmine-browser-runner": "2.2.0", - "jasmine-core": "5.1.1", "js-base64": "3.7.5", "nodemon": "2.0.20", - "tsup": "6.1.3", - "typescript": "4.7.4" + "playwright": "^1.48.2", + "tsup": "8.3.0", + "typescript": "4.7.4", + "vite-plugin-arraybuffer": "^0.0.8", + "vitest": "^2.1.3" }, "publishConfig": { "access": "public" - }, - "packageManager": "pnpm@7.1.6" + } } diff --git a/packages/dotlottie-js/playground.js b/packages/dotlottie-js/playground.js new file mode 100644 index 00000000..0804a04c --- /dev/null +++ b/packages/dotlottie-js/playground.js @@ -0,0 +1,68 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +/** + * This is a playground for creating/testing .lottie files. + */ + +import fs from 'fs'; + +import { DotLottie } from './dist/index.node.js'; +import BALL_DATA from './src/__tests__/__fixtures__/ball.json' assert { type: 'json' }; + +async function main() { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ + id: 'ball', + data: BALL_DATA, + }); + + dotLottie.addTheme({ + id: 'dark', + data: { + rules: [ + { id: 'ball_color', type: 'Color', value: [0, 1, 0, 1] }, + { + id: 'bg_color', + type: 'Color', + keyframes: [ + { + frame: 0, + value: [0, 0, 0, 1], + inTangent: { x: 0.6, y: 0.6 }, + outTangent: { x: 0.6, y: 0.6 }, + }, + { frame: 60, value: [1, 1, 1, 1] }, + ], + }, + ], + }, + }); + + dotLottie.addTheme({ + id: 'light', + data: { + rules: [ + { + id: 'ball_color', + type: 'Color', + keyframes: [ + { frame: 0, value: [0, 0, 0, 1], inTangent: { x: 0.6, y: 0.6 }, outTangent: { x: 0.6, y: 0.6 } }, + { frame: 60, value: [1, 1, 1, 1] }, + ], + }, + { id: 'bg_color', type: 'Color', value: [0, 0.6, 0.6, 1] }, + ], + }, + }); + + await dotLottie.build(); + + const buffer = await dotLottie.toArrayBuffer(); + + fs.writeFileSync('output.lottie', new Uint8Array(buffer)); +} + +main(); diff --git a/packages/dotlottie-js/src/tests/__fixtures__/audio/2_instrument_animations.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/audio/2_instrument_animations.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/audio/2_instrument_animations.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/audio/2_instrument_animations.lottie diff --git a/packages/dotlottie-js/src/tests/__fixtures__/audio/instruments_1.json b/packages/dotlottie-js/src/__tests__/__fixtures__/audio/instruments_1.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/audio/instruments_1.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/audio/instruments_1.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/audio/instruments_2.json b/packages/dotlottie-js/src/__tests__/__fixtures__/audio/instruments_2.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/audio/instruments_2.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/audio/instruments_2.json diff --git a/packages/dotlottie-js/src/__tests__/__fixtures__/ball.json b/packages/dotlottie-js/src/__tests__/__fixtures__/ball.json new file mode 100644 index 00000000..49dc5a1e --- /dev/null +++ b/packages/dotlottie-js/src/__tests__/__fixtures__/ball.json @@ -0,0 +1,738 @@ +{ + "v": "4.8.0", + "meta": { + "g": "LottieFiles AE ", + "a": "", + "k": "", + "d": "", + "tc": "" + }, + "fr": 30, + "ip": 0, + "op": 61, + "w": 1080, + "h": 1080, + "nm": "B", + "assets": [], + "layers": [ + { + "ind": 1, + "ty": 4, + "nm": "B", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 0 + ] + }, + { + "t": 58, + "s": [ + 349.032 + ] + } + ] + }, + "p": { + "s": true, + "x": { + "a": 0, + "k": 540 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.67 + ], + "y": [ + 0.343 + ] + }, + "o": { + "x": [ + 0.33 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 152 + ] + }, + { + "i": { + "x": [ + 0.67 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.33 + ], + "y": [ + 0 + ] + }, + "t": 15, + "s": [ + 915 + ] + }, + { + "i": { + "x": [ + 0.67 + ], + "y": [ + 0.343 + ] + }, + "o": { + "x": [ + 0.33 + ], + "y": [ + 0 + ] + }, + "t": 30, + "s": [ + 152 + ] + }, + { + "i": { + "x": [ + 0.67 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.33 + ], + "y": [ + 0 + ] + }, + "t": 45, + "s": [ + 915 + ] + }, + { + "t": 60, + "s": [ + 152 + ] + } + ] + } + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 270, + 270 + ] + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 15, + "s": [ + 135 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 30, + "s": [ + 33.75 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 45, + "s": [ + 33.75 + ] + }, + { + "t": 60, + "s": [ + 135 + ] + } + ] + }, + "nm": "S" + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.961, + 0.761, + 0.267, + 1 + ], + "sid": "ball_color" + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1, + "nm": "C" + } + ], + "ip": 0, + "op": 61, + "st": 0 + }, + { + "ind": 2, + "ty": 4, + "nm": "S", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 10 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 15, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 30, + "s": [ + 10 + ] + }, + { + "i": { + "x": [ + 0.667 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 45, + "s": [ + 100 + ] + }, + { + "t": 60, + "s": [ + 10 + ] + } + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 540, + 1051, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 50, + 50, + 100 + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.667, + 0.667 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 0, + "s": [ + 270, + 13.5 + ] + }, + { + "i": { + "x": [ + 0.667, + 0.667 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 15, + "s": [ + 540, + 13.5 + ] + }, + { + "i": { + "x": [ + 0.667, + 0.667 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 30, + "s": [ + 270, + 13.5 + ] + }, + { + "i": { + "x": [ + 0.667, + 0.667 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 45, + "s": [ + 540, + 13.5 + ] + }, + { + "t": 60, + "s": [ + 270, + 13.5 + ] + } + ] + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 790 + }, + "nm": "R" + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.114, + 0.114, + 0.114, + 1 + ], + "sid": "bg_color" + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1, + "nm": "F" + } + ], + "ip": 0, + "op": 61, + "st": 0 + }, + { + "ind": 3, + "ty": 4, + "nm": "B", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100 + }, + "r": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 540, + 540, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ] + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 1080, + 1080 + ] + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "nm": "R" + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.153, + 0.153, + 0.153, + 1 + ] + }, + "o": { + "a": 0, + "k": 100 + }, + "r": 1, + "nm": "F" + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ] + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ] + }, + "r": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100 + }, + "sk": { + "a": 0, + "k": 0 + }, + "sa": { + "a": 0, + "k": 0 + }, + "nm": "T" + } + ], + "nm": "G" + } + ], + "ip": 0, + "op": 61, + "st": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/bull.json b/packages/dotlottie-js/src/__tests__/__fixtures__/bull.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/bull.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/bull.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/bull.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/bull.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/bull.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/bull.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-1.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-1.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/image-animation-layer-2.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/lots-of-dupes.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/lots-of-dupes.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/simple-image-animation.json b/packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/image-asset-optimization/simple-image-animation.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/mimetype-tests/mp-3-test.txt b/packages/dotlottie-js/src/__tests__/__fixtures__/mimetype-tests/mp-3-test.txt similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/mimetype-tests/mp-3-test.txt rename to packages/dotlottie-js/src/__tests__/__fixtures__/mimetype-tests/mp-3-test.txt diff --git a/packages/dotlottie-js/src/tests/__fixtures__/mimetype-tests/svg-xml-test.txt b/packages/dotlottie-js/src/__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/mimetype-tests/svg-xml-test.txt rename to packages/dotlottie-js/src/__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation.lottie diff --git a/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/bull.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/bull.json new file mode 100644 index 00000000..5998b7f9 --- /dev/null +++ b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/bull.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"Jonhson Subianto","k":"Bulldog, Bull, Nap, Sleep, shit","d":"Bulldog and Bull","tc":""},"fr":60,"ip":0,"op":180,"w":500,"h":500,"nm":"bulldog sleep","ddd":0,"assets":[{"id":"image_0","w":113,"h":111,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_1","w":112,"h":151,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_2","w":112,"h":151,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_3","w":222,"h":176,"u":"","p":"data:image/png;base64,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","e":1},{"id":"image_4","w":194,"h":126,"u":"","p":"data:image/png;base64,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","e":1}],"fonts":{"list":[{"fName":"Roboto-BoldItalic","fFamily":"Roboto","fStyle":"Bold Italic","ascent":75}]},"layers":[{"ddd":0,"ind":1,"ty":5,"nm":"z","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":9,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[100]},{"t":60,"s":[0]}],"ix":11},"r":{"a":0,"k":-31.005,"ix":10},"p":{"x:":"}]; alert(\"BAD\");[function _expression_function(){","a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[337.5,181.5,0],"to":[7.625,-3.708,0],"ti":[-7.625,3.708,0]},{"t":60,"s":[383.25,159.25,0]}],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[45.548,45.548,100]},{"t":60,"s":[87.176,87.176,100]}],"ix":6}},"ao":0,"t":{"d":{"k":[{"s":{"s":36,"f":"Roboto-BoldItalic","t":"z","j":0,"tr":0,"lh":43.2,"ls":0,"fc":[0.298,0.125,0.027]},"t":0}]},"p":{},"m":{"g":1,"a":{"a":0,"k":[0,0],"ix":2}},"a":[]},"ip":0,"op":61,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"bulldog hand Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[333,244,0],"ix":2},"a":{"a":0,"k":[20.5,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.452,-11.54],[11.076,-3.133],[0.621,4.533],[3.312,-0.822],[0.052,3.864],[3.053,-0.14],[-2.122,7.307]],"o":[[0,0],[0,0],[0,0],[-3.311,0.823],[0,0],[-3.053,0.141],[2.121,-7.306]],"v":[[14.874,-0.281],[8.234,10.772],[4.596,6.903],[1.594,10.998],[-7.357,7.957],[-11.186,10.556],[-17.188,1.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[21.245,10.48],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.452,-11.54],[11.076,-3.133],[0.621,4.533],[3.312,-0.822],[0.052,3.864],[3.053,-0.14],[-2.122,7.307]],"o":[[0,0],[0,0],[0,0],[-3.311,0.823],[0,0],[-3.053,0.141],[2.121,-7.306]],"v":[[14.876,-0.952],[8.236,10.101],[4.598,6.232],[1.597,10.327],[-7.354,7.286],[-11.183,9.885],[-17.185,0.33]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[21.242,11.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"bulldog hand Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[208,251,0],"ix":2},"a":{"a":0,"k":[20.5,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.452,-11.54],[11.076,-3.133],[0.621,4.533],[3.312,-0.822],[0.052,3.864],[3.053,-0.14],[-2.122,7.307]],"o":[[0,0],[0,0],[0,0],[-3.311,0.823],[0,0],[-3.053,0.141],[2.121,-7.306]],"v":[[14.874,-0.281],[8.234,10.772],[4.596,6.903],[1.594,10.998],[-7.357,7.957],[-11.186,10.556],[-17.188,1.001]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[21.245,10.48],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-5.452,-11.54],[11.076,-3.133],[0.621,4.533],[3.312,-0.822],[0.052,3.864],[3.053,-0.14],[-2.122,7.307]],"o":[[0,0],[0,0],[0,0],[-3.311,0.823],[0,0],[-3.053,0.141],[2.121,-7.306]],"v":[[14.876,-0.952],[8.236,10.101],[4.598,6.232],[1.597,10.327],[-7.354,7.286],[-11.183,9.885],[-17.185,0.33]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[21.242,11.15],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"buffalo head Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":120,"s":[-4.688]},{"t":150,"s":[0]}],"ix":10},"p":{"a":0,"k":[267,227,0],"ix":2},"a":{"a":0,"k":[59,58,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.799,0.798],[3.799,-0.798]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4.478,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[82.281,40.206],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-3.484,1.645],[3.484,-1.646]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4.478,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[37.084,56.94],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[4.187,-17.689],[13.748,-2.381],[0.368,1.121],[0.501,0.676],[-8.119,-3.579],[-0.827,1.476],[1.546,0.681],[2.664,0.66],[-0.01,0.034],[2.048,0.199],[0.006,-0.015],[0.974,0.055],[1.93,-0.202],[-0.002,-0.024],[3.652,-1.207],[0,0],[3.663,-1.785],[0.607,-0.325],[0.131,-0.072],[1.659,-1.185],[-0.005,-0.009],[1.483,-1.387],[0.031,0.02],[0.926,-1.169],[-0.028,-0.02],[1.374,-2.408],[-1.539,-0.693],[-0.839,1.471],[-8.025,3.869],[-0.273,-0.833],[-0.766,-0.83],[-0.469,-12.668],[-3.116,18.316],[0,0],[0.159,5.447],[0,0],[-0.381,0.126],[-4.707,-0.723],[-4.095,4.133],[-3.061,1.012],[-0.986,0.05],[-0.012,0.001],[0,0],[-4.41,3.855],[0,0]],"o":[[-7.114,-10.506],[0.125,-1.123],[-0.273,-0.833],[8.397,-1.605],[1.546,0.682],[0.827,-1.475],[-2.523,-1.113],[0.01,-0.034],[-2.024,-0.524],[-0.006,0.014],[-0.966,-0.111],[-4.058,-0.173],[0.001,0.023],[-5.516,0.732],[0,0],[-2.994,0.989],[-0.645,0.317],[-0.128,0.069],[-2.114,1.154],[0.005,0.01],[-1.652,1.206],[-0.031,-0.018],[-1.956,1.8],[0.028,0.021],[-1.758,2.127],[-0.839,1.47],[1.54,0.693],[4.261,-7.468],[-0.003,0.842],[0.368,1.12],[-12.445,6.271],[-14.025,-11.863],[0,0],[5.829,0.45],[0,0],[0.535,-0.364],[3.061,-1.011],[5.742,0.882],[3.358,-3.388],[0.673,-0.222],[0.012,0],[0,0],[3.113,4.497],[0,0],[13.032,12.717]],"v":[[51.035,12.769],[16.662,-0.802],[16.315,-4.196],[15.14,-6.463],[40.58,-3.711],[44.877,-5.149],[43.575,-9.054],[35.779,-11.711],[35.807,-11.813],[29.636,-12.867],[29.62,-12.823],[26.71,-13.071],[17.16,-12.772],[17.163,-12.702],[4.466,-9.478],[-1.618,-7.468],[-11.244,-3.783],[-13.122,-2.819],[-13.504,-2.616],[-19.145,0.918],[-19.127,0.945],[-23.831,4.839],[-23.919,4.776],[-28.204,9.34],[-28.126,9.406],[-32.837,16.215],[-31.569,20.132],[-27.262,18.724],[-8.418,1.322],[-8.021,3.846],[-6.29,6.783],[-25.902,38.123],[-43.287,-9.576],[-38.091,-9.263],[-27.233,-18.815],[-27.552,-29.799],[-26.089,-30.655],[-14.703,-31.077],[1.008,-36.269],[10.416,-42.718],[13.006,-43.038],[13.042,-43.04],[19.192,-34.156],[33.611,-32.972],[37.044,-35.972]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[3.354,-1.109],[1.001,3.05],[0,0],[0,0],[10.748,-2.059],[0,0],[0,0],[-4.539,-13.828],[0,0]],"o":[[-3.355,1.108],[0,0],[0,0],[-3.196,-9.737],[0,0],[0,0],[15.096,-4.988],[0,0],[1.001,3.051]],"v":[[45.939,33.546],[37.304,28.931],[36.396,26.165],[36.335,25.98],[11.455,11.667],[9.913,7.118],[12.955,6.112],[49.299,23.237],[50.207,26.003]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[5.032,-1.663],[0,0],[1.502,4.576],[0,0],[-8.387,2.771],[-2.503,-7.626],[0,0]],"o":[[0,0],[-5.032,1.662],[0,0],[-2.503,-7.626],[8.387,-2.771],[0,0],[1.502,4.576]],"v":[[23.909,39.489],[11.741,43.51],[-0.108,38.227],[-0.169,38.043],[10.164,18.006],[30.251,27.991],[30.312,28.175]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-1.677,0.554],[0,0],[-0.5,-1.525],[1.677,-0.555],[0,0],[0.501,1.525]],"o":[[0,0],[1.678,-0.555],[0.501,1.525],[0,0],[-1.677,0.554],[-0.501,-1.525]],"v":[[0.197,-1.936],[6.281,-3.946],[10.231,-2.186],[8.097,1.586],[2.013,3.596],[-1.937,1.835]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[-3.196,-9.737],[0,0],[0,0],[3.355,-1.109],[1.001,3.051],[0,0],[-15.096,4.989],[0,0],[0,0]],"o":[[0,0],[0,0],[1.001,3.05],[-3.355,1.108],[0,0],[-4.538,-13.828],[0,0],[0,0],[-9.863,4.752]],"v":[[-6.253,40.053],[-6.192,40.238],[-5.284,43.004],[-9.77,51.487],[-17.67,47.965],[-18.578,45.199],[0.807,10.126],[3.849,9.121],[5.391,13.671]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[0,0],[-1.807,1.584],[-0.013,0.011],[0,0],[-0.594,-2.127],[0,0],[1.956,0.158],[0.023,0.002]],"o":[[-2.136,-0.655],[0.013,-0.011],[0,0],[1.755,-1.517],[0,0],[0.053,1.823],[-0.023,-0.002],[0,0]],"v":[[-50.574,-15.935],[-51.503,-20.81],[-51.464,-20.844],[-39.271,-31.868],[-33.923,-30.12],[-33.58,-18.313],[-37.212,-15.13],[-37.28,-15.135]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[-2.298,-0.153],[0,0],[-0.037,-0.004],[1.36,-1.809],[0,0],[1.039,1.499],[0,0]],"o":[[0,0],[0.038,0.004],[2.395,0.196],[0,0],[-1.478,1.292],[0,0],[-0.779,-2.056]],"v":[[21.12,-51.834],[36.734,-50.016],[36.847,-50.004],[38.97,-45.507],[29.401,-37.142],[24.594,-37.536],[17.879,-47.238]],"c":true},"ix":2},"nm":"Path 7","mn":"ADBE Vector Shape - Group","hd":false},{"ind":7,"ty":"sh","ix":8,"ks":{"a":0,"k":{"i":[[0,0],[2.604,11.088],[7.756,7.57],[-0.208,0.245],[7.294,0.642],[0,0],[0.037,0.003],[0.481,-5.03],[0.956,-0.316],[2.608,-2.632],[3.62,0.556],[5.446,-1.8],[0.545,-0.313],[4.138,-3.627],[0.013,-0.011],[0,0],[-7.057,-1.666],[-2.293,-0.138],[-4.014,-9.65],[-10.109,-6.6],[0,0],[-6.71,2.217],[-1.282,3.55],[-4.523,1.494],[0,0],[-1.85,3.78],[-4.088,1.351],[2.002,6.101]],"o":[[4.171,-11.042],[-2.449,-10.426],[1.77,-1.547],[4.678,-5.535],[0,0],[-0.038,-0.004],[-5.536,-0.454],[-0.887,0.086],[-5.446,1.8],[-2.582,2.606],[-3.656,-0.562],[-0.668,0.22],[-3.397,-3.674],[-0.013,0.011],[0,0],[-5.513,4.858],[0.355,0.084],[-1.798,10.752],[4.351,10.464],[0,0],[2.003,6.1],[4.088,-1.351],[3.727,1.937],[0,0],[4.523,-1.495],[3.133,2.091],[6.71,-2.217],[0,0]],"v":[[55.185,20.522],[57.367,-12.695],[41.796,-40.127],[43.75,-41.884],[37.835,-55.863],[22.205,-57.683],[22.092,-57.694],[11.377,-48.803],[8.6,-48.25],[-3.551,-40.111],[-13.311,-36.886],[-27.904,-36.187],[-29.719,-35.36],[-43.504,-36.018],[-43.543,-35.984],[-55.741,-24.955],[-52.245,-10.163],[-49.593,-9.955],[-46.232,21.126],[-24.146,47.178],[-23.754,49.976],[-7.954,57.019],[0.649,48.136],[13.557,49.042],[25.725,45.021],[35.571,36.596],[47.754,39.078],[56.291,23.993]],"c":true},"ix":2},"nm":"Path 8","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[58.925,57.727],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":12,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-21.005,-8.039]],"o":[[0,0],[0,0]],"v":[[-13.462,9.069],[13.462,-1.03]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":6.822,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[57.692,34.499],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":2,"nm":"bulldog-head-texture.png","cl":"png","refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":120,"s":[-4.688]},{"t":150,"s":[0]}],"ix":10},"p":{"a":0,"k":[267,226,0],"ix":2},"a":{"a":0,"k":[56.5,55.5,0],"ix":1},"s":{"a":0,"k":[100,95.495,100],"ix":6}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"right horn Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[132,219,0],"ix":2},"a":{"a":0,"k":[62,87,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.773,-1.802],[7.395,2.077],[4.229,4.817],[-1.211,4.312],[-2.74,1.827],[-3.201,-3.732],[-6.289,-1.767],[-5.343,1.737],[-1.795,3.519],[0.768,-2.734]],"o":[[-6.119,1.91],[-7.395,-2.078],[-3.991,-4.545],[0.768,-2.735],[-0.3,3.938],[3.658,4.265],[6.29,1.766],[4.677,-1.519],[1.388,2.987],[-1.211,4.312]],"v":[[16.311,23.843],[-4.646,23.584],[-22.673,12.893],[-26.984,-0.841],[-21.619,-7.777],[-17.196,3.99],[-1.77,13.344],[16.27,13.39],[26.174,5.647],[27.142,14.362]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.495,1.239],[-2.143,-2.498],[-4.824,-1.355],[-4.114,1.337],[-1.519,2.211],[0.472,-1.68],[4.172,-1.356],[5.433,1.526],[3.079,3.589],[-0.885,3.15]],"o":[[0.145,2.678],[2.816,3.283],[4.825,1.355],[3.13,-1.017],[0.631,1.836],[-0.885,3.15],[-4.497,1.462],[-5.433,-1.526],[-2.856,-3.33],[0.472,-1.68]],"v":[[-13.798,-13.446],[-10.331,-5.552],[1.517,1.641],[15.378,1.669],[22.449,-3.265],[22.704,2.068],[14.862,9.055],[-0.537,8.955],[-13.736,1.023],[-16.793,-9.026]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-7.27,-1.063],[0,0],[-3.199,-1.307],[0.579,-2.06],[7.731,2.172],[-1.337,4.757]],"o":[[0,0],[-0.941,3.347],[6.372,2.603],[-1.336,4.758],[-7.731,-2.172],[1.892,-6.739]],"v":[[6.639,-25.021],[6.403,-24.18],[10.365,-16.016],[19.478,-7.519],[2.75,-2.748],[-9.047,-15.531]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[4.053,5.32],[2.339,2.99],[6.26,2.557],[-0.313,1.116],[0,0],[0,0],[4.37,-2.262],[1.633,-3.453],[1.595,-2.338],[1.484,-5.282],[-5.044,-5.743],[-8.234,-2.313],[-6.95,2.17],[-1.643,5.847]],"o":[[-0.16,-3.143],[-1.19,-3.773],[-1.053,-0.43],[0,0],[0,0],[-5.093,-1.431],[-3.481,1.802],[-3.052,1.333],[-6.23,2.431],[-1.643,5.848],[4.804,5.471],[8.235,2.313],[7.296,-2.277],[1.484,-5.283]],"v":[[27.545,-0.805],[23.76,-10.152],[12.089,-20.236],[10.791,-22.948],[12.299,-28.317],[10.105,-28.933],[-4.569,-27.643],[-12.383,-19.582],[-19.431,-13.999],[-31.372,-2.074],[-26.098,15.901],[-5.879,27.972],[17.669,28.194],[31.531,15.595]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.184,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[32.544,30.342],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":6,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[5.773,-1.802],[7.395,2.077],[4.229,4.817],[-1.211,4.312],[-2.74,1.827],[-3.201,-3.732],[-6.289,-1.767],[-5.343,1.737],[-1.795,3.519],[0.768,-2.734]],"o":[[-6.119,1.91],[-7.395,-2.078],[-3.991,-4.545],[0.768,-2.735],[-0.3,3.938],[3.658,4.265],[6.29,1.766],[4.677,-1.519],[1.388,2.987],[-1.211,4.312]],"v":[[16.076,23.832],[-4.881,23.573],[-22.908,12.882],[-27.219,-0.852],[-21.854,-7.788],[-17.431,3.979],[-2.005,13.333],[16.035,13.379],[25.939,5.636],[26.907,14.351]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.495,1.239],[-2.143,-2.498],[-4.824,-1.355],[-4.114,1.337],[-1.519,2.211],[0.472,-1.68],[4.172,-1.356],[5.433,1.526],[3.079,3.589],[-0.885,3.15]],"o":[[0.145,2.678],[2.816,3.283],[4.825,1.355],[3.13,-1.017],[0.631,1.836],[-0.885,3.15],[-4.497,1.462],[-5.433,-1.526],[-2.856,-3.33],[0.472,-1.68]],"v":[[-14.033,-13.457],[-10.566,-5.563],[1.282,1.63],[15.143,1.658],[22.214,-3.276],[22.469,2.057],[14.627,9.044],[-0.772,8.944],[-13.971,1.012],[-17.028,-9.037]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-7.27,-1.063],[0,0],[-3.199,-1.307],[0.579,-2.06],[7.731,2.172],[-1.337,4.757]],"o":[[0,0],[-0.941,3.347],[6.372,2.603],[-1.336,4.758],[-7.731,-2.172],[1.892,-6.739]],"v":[[6.404,-25.032],[6.168,-24.191],[10.13,-16.027],[19.243,-7.53],[2.515,-2.759],[-9.282,-15.542]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[4.053,5.32],[2.339,2.99],[6.26,2.557],[-0.313,1.116],[0,0],[0,0],[4.37,-2.262],[1.633,-3.453],[1.595,-2.338],[1.484,-5.282],[-5.044,-5.743],[-8.234,-2.313],[-6.95,2.17],[-1.643,5.847]],"o":[[-0.16,-3.143],[-1.19,-3.773],[-1.053,-0.43],[0,0],[0,0],[-5.093,-1.431],[-3.481,1.802],[-3.052,1.333],[-6.23,2.431],[-1.643,5.848],[4.804,5.471],[8.235,2.313],[7.296,-2.277],[1.484,-5.283]],"v":[[27.31,-0.816],[23.525,-10.163],[11.854,-20.247],[10.556,-22.959],[12.064,-28.328],[9.87,-28.944],[-4.804,-27.654],[-12.618,-19.593],[-19.666,-14.01],[-31.607,-2.085],[-26.333,15.89],[-6.114,27.961],[17.434,28.183],[31.296,15.584]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[32.78,30.353],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":8,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.777,3.159],[3.554,-5.331],[0,0],[0,0],[0,0],[-0.197,-4.554],[-28.825,-4.344],[0.788,12.285],[0,0],[0,0],[3.158,1.777]],"o":[[0,0],[-3.554,5.33],[0,0],[0,0],[0,0],[0.198,4.554],[0,0],[-0.789,-12.284],[0,0],[0,0],[-3.159,-1.777]],"v":[[6.417,-27.641],[-9.97,-20.927],[-15.893,-15.005],[-19.249,-12.044],[-23.988,-8.688],[-30.108,-0.408],[2.271,27.641],[29.517,13.228],[24.778,-1.776],[22.212,-10.266],[12.143,-18.953]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1.184,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[32.849,30.758],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-12.754,17.138],[16.339,21.785],[0,0],[13.825,-50.171],[-15.504,-10.055],[0,0]],"o":[[0,0],[-16.339,-21.786],[0,0],[-13.826,50.17],[15.505,10.054],[0,0]],"v":[[59.603,36.175],[-11.842,9.742],[-20.64,-63.575],[-45.777,-33.306],[-33.386,64.625],[34.661,83.477]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[60.091,86.575],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":2,"nm":"right-horn-texture.png","cl":"png","refId":"image_1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[134,229,0],"ix":2},"a":{"a":0,"k":[56,75.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"left horn Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[394,220,0],"ix":2},"a":{"a":0,"k":[59.5,79.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[12.754,17.138],[-16.339,21.785],[0,0],[-13.825,-50.171],[15.504,-10.055],[0,0]],"o":[[0,0],[16.339,-21.786],[0,0],[13.826,50.17],[-15.505,10.054],[0,0]],"v":[[-59.603,36.175],[11.842,9.742],[20.64,-63.575],[45.777,-33.306],[33.386,64.625],[-34.661,83.477]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[63.156,71.023],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":2,"nm":"left-horn-texture.png","cl":"png","refId":"image_2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[394,220,0],"ix":2},"a":{"a":0,"k":[56,75.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"buffalo head Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[274,344,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":60,"s":[274,344,0],"to":[0,-2,0],"ti":[0,2,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":90,"s":[274,332,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":120,"s":[274,332,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":150,"s":[274,332,0],"to":[0,2,0],"ti":[0,-2,0]},{"t":179,"s":[274,344,0]}],"ix":2},"a":{"a":0,"k":[82,40,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.456],[4.456,0],[0,4.456],[-4.456,0]],"o":[[0,4.456],[-4.456,0],[0,-4.456],[4.456,0]],"v":[[8.069,0],[0,8.069],[-8.069,0],[0,-8.069]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[155.727,19.691],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-4.456],[4.456,0],[0,4.456],[-4.456,0]],"o":[[0,4.456],[-4.456,0],[0,-4.456],[4.456,0]],"v":[[8.069,0],[0,8.069],[-8.069,0],[0,-8.069]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[8.069,19.691],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[9.488,35.962],[-9.488,-35.962]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[115.069,39.516],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-9.488,35.962],[9.488,-35.962]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[43.18,39.516],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":3,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":2,"nm":"buffalo-head-texture.png","cl":"png","refId":"image_3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[274,296,0],"ix":2},"a":{"a":0,"k":[111,88,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"buffalo right ear Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[19.107]},{"t":120,"s":[0]}],"ix":10},"p":{"a":0,"k":[163,297,0],"ix":2},"a":{"a":0,"k":[62.5,10,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.096,2.557],[7.297,0],[0,0],[3.01,1.21],[-3.555,3.496],[-2.442,0],[-5.332,-3.196],[-0.73,2.3],[6.832,0],[2.919,-1.035],[0.37,-0.367],[8.227,-6.29],[-0.283,-0.836],[-4.158,0],[0,0],[-13.153,4.953]],"o":[[-12.881,5.49],[0,0],[-9.776,-0.001],[8.712,-7.12],[1.984,-0.608],[6.554,0],[0.532,-2.405],[-6.376,-3.42],[-3.552,0],[-0.491,0.175],[-0.234,0.229],[-3.221,2.462],[2.552,7.54],[0,0],[7.319,0],[-0.036,-2.565]],"v":[[28.733,9.466],[-5.186,15.815],[-5.188,15.815],[-24.634,13.189],[5.476,-14.899],[12.139,-15.815],[31.146,-10.392],[33.032,-17.457],[12.139,-22.92],[2.385,-21.36],[1.078,-20.539],[-30.509,8.782],[-32.775,14.998],[-5.189,22.921],[-5.186,22.921],[28.647,17.156]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[33.032,22.92],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":2,"nm":"body-texture.png","cl":"png","refId":"image_4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[136.5,305.5,0],"ix":2},"a":{"a":0,"k":[97,63,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"ip":0,"op":180,"st":0,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"buffalo left ear Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":90,"s":[-25.56]},{"t":120,"s":[0]}],"ix":10},"p":{"a":0,"k":[377,293,0],"ix":2},"a":{"a":0,"k":[14,13,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-20.315,-7.206],[-8.5,-6.498],[28.541,13.611]],"o":[[0,0],[8.501,6.499],[-28.542,-13.611]],"v":[[8.592,-18.01],[40.516,11.605],[-20.475,11.605]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.299765792547,0.126458770154,0.02816657459,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":7.105,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[34.395,22.919],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-20.315,-7.206],[-8.5,-6.498],[28.541,13.611]],"o":[[0,0],[8.501,6.499],[-28.542,-13.611]],"v":[[3.186,-18.012],[35.11,11.603],[-25.882,11.603]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.999999940162,0.999999940162,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[39.802,22.921],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[{"tm":0,"cm":"1","dr":0},{"tm":30,"cm":"2","dr":0},{"tm":60,"cm":"3","dr":0},{"tm":90,"cm":"4","dr":0},{"tm":120,"cm":"5","dr":0},{"tm":150,"cm":"6","dr":0},{"tm":179,"cm":"7","dr":0}],"chars":[{"ch":"z","size":36,"style":"Bold Italic","w":49.85,"data":{"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[47.607,-43.848],[49.023,-52.832],[7.861,-52.832],[6.055,-41.406],[29.15,-41.309],[0.342,-9.229],[-1.123,0],[41.504,0],[43.311,-11.377],[18.506,-11.475]],"c":true},"ix":2},"nm":"z","mn":"ADBE Vector Shape - Group","hd":false}],"nm":"z","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}]},"fFamily":"Roboto"}]} \ No newline at end of file diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/lottie1.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/lottie1.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/lottie1.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/lottie1.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/pigeon.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/pigeon.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/pigeon.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/pigeon.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/smiley.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/smiley.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/smiley.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/smiley.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/wifi.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/wifi.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/animations/wifi.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/animations/wifi.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/manifest.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/manifest.json similarity index 58% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/animation/manifest.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/manifest.json index fa608434..a16520d5 100644 --- a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/manifest.json +++ b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/animation/manifest.json @@ -1,9 +1,8 @@ { - "version": "1.0", - "revision": 1, + "version": "1", "keywords": "dotLottie", "author": "LottieFiles", - "generator": "@dotlottie/dotlottie-js/node@0.6.2", + "generator": "@dotlottie/dotlottie-js@0.9.0", "animations": [ { "id": "lottie1", @@ -15,13 +14,5 @@ "hover": false, "intermission": 0 } - ], - "themes": [ - { - "id": "theme1", - "animations": [ - "lottie1" - ] - } ] } \ No newline at end of file diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/big-merged-dotlottie.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/big-merged-dotlottie.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/big-merged-dotlottie.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/big-merged-dotlottie.lottie diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/bull.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/bull.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/bull.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/bull.lottie diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings.lottie diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings/animations/lottie01.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings/animations/lottie01.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings/manifest.json b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings/manifest.json similarity index 79% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings/manifest.json rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings/manifest.json index dadf059d..d362a803 100644 --- a/packages/dotlottie-js/src/tests/__fixtures__/simple/edited-settings/manifest.json +++ b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/edited-settings/manifest.json @@ -1,8 +1,8 @@ { - "version": "2.0", + "version": "1", "keywords": "dotLottie", "author": "LottieFiles", - "generator": "generator", + "generator": "@dotlottie/dotlottie-js@0.9.0", "animations": [ { "id": "lottie01", @@ -14,6 +14,5 @@ "hover": false, "intermission": 0 } - ], - "revision": 1 + ] } \ No newline at end of file diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/exploding_pigeon.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/exploding_pigeon.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/exploding_pigeon.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/exploding_pigeon.lottie diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/state/pigeon-state.ts b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/state/pigeon-state.ts similarity index 84% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/state/pigeon-state.ts rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/state/pigeon-state.ts index 8f90eb6c..d8bde9b7 100644 --- a/packages/dotlottie-js/src/tests/__fixtures__/simple/state/pigeon-state.ts +++ b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/state/pigeon-state.ts @@ -1,15 +1,17 @@ -import { DotLottieStateMachine } from "../../../../common" +import { DotLottieStateMachine } from "../../../../v2/common/schemas/state-machine" -export const PigeonState: DotLottieStateMachine = { - descriptor: { - id: "explodingPigeon", - initial: "pigeonRunning" - }, +export const PigeonState: { + id: string; + data: DotLottieStateMachine; +} = { + id: "explodingPigeon", + data: { + initial: "pigeonRunning", states: [ { type: "PlaybackState", name: "pigeonRunning", - animationId: "", + animation: "", loop: true, autoplay: true, segment: "bird", @@ -29,7 +31,7 @@ export const PigeonState: DotLottieStateMachine = { { type: "PlaybackState", name: "explosion", - animationId: "", + animation: "", loop: false, autoplay: true, segment: "explosion", @@ -49,7 +51,7 @@ export const PigeonState: DotLottieStateMachine = { { type: "PlaybackState", name: "feathersFalling", - animationId: "", + animation: "", loop: false, autoplay: true, segment: "feathers", @@ -110,21 +112,23 @@ export const PigeonState: DotLottieStateMachine = { type: "Event", name: "restart" } - ] + ] + } } -export const PigeonWithoutExplosion: DotLottieStateMachine = -{ - descriptor: { - id: "pigeonWithoutExplosion", - initial: "pigeonRunning" - }, +export const PigeonWithoutExplosion: { + id: string; + data: DotLottieStateMachine; +} = { + id: "pigeonWithoutExplosion", + data: { + initial: "pigeonRunning", states: [ { type: "PlaybackState", name: "pigeonRunning", - animationId: "", + animation: "", loop: true, autoplay: true, segment: "bird", @@ -144,7 +148,7 @@ export const PigeonWithoutExplosion: DotLottieStateMachine = { type: "PlaybackState", name: "feathersFalling", - animationId: "", + animation: "", loop: false, autoplay: true, segment: "feathers", @@ -195,19 +199,22 @@ export const PigeonWithoutExplosion: DotLottieStateMachine = type: "Event", name: "restart" } - ] + ] + } } -export const SmileyWifi: DotLottieStateMachine = { - descriptor: { - id: 'simple_click_to_next_prev', +export const SmileyWifi: { + id: string; + data: DotLottieStateMachine; +} = { + id: 'simple_click_to_next_prev', + data: { initial: "smiley", - }, states: [ { name: "smiley", type: "PlaybackState", - animationId: 'smiley', + animation: 'smiley', autoplay: true, loop: true, mode: "Reverse", @@ -228,7 +235,7 @@ export const SmileyWifi: DotLottieStateMachine = { { name: "wifi", type: "PlaybackState", - animationId: 'wifi', + animation: 'wifi', autoplay: true, loop: true, mode: "Forward", @@ -238,5 +245,6 @@ export const SmileyWifi: DotLottieStateMachine = { triggers: [{ type: "Event", name: "click" - }] + }] + }, }; diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/video-embedded.lottie b/packages/dotlottie-js/src/__tests__/__fixtures__/simple/video-embedded.lottie similarity index 100% rename from packages/dotlottie-js/src/tests/__fixtures__/simple/video-embedded.lottie rename to packages/dotlottie-js/src/__tests__/__fixtures__/simple/video-embedded.lottie diff --git a/packages/dotlottie-js/src/__tests__/browser/index.spec.ts b/packages/dotlottie-js/src/__tests__/browser/index.spec.ts new file mode 100644 index 00000000..13721092 --- /dev/null +++ b/packages/dotlottie-js/src/__tests__/browser/index.spec.ts @@ -0,0 +1,224 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import type { Animation } from '@lottie-animation-community/lottie-types'; +import { strFromU8, unzipSync } from 'fflate'; +import { expect, test, describe } from 'vitest'; + +import { PACKAGE_NAME } from '../../constants'; +import { makeDotLottie } from '../../index.browser'; +import { DotLottieV1, toDotLottieV1 } from '../../v1/browser'; +import { DotLottie, toDotLottieV2 } from '../../v2/browser'; +import bull from '../__fixtures__/bull.json'; + +const bullAnimationData = bull as Animation; + +describe('makeDotLottie', () => { + test('make dotLottie v1', () => { + const dotLottie = makeDotLottie('v1'); + + expect(dotLottie.version).toBe('1'); + + expect(dotLottie).toBeInstanceOf(DotLottieV1); + }); + + test('make dotLottie v2', () => { + const dotLottie = makeDotLottie('v2'); + + expect(dotLottie.version).toBe('2'); + + expect(dotLottie).toBeInstanceOf(DotLottie); + }); +}); + +describe('toDotLottieV2', () => { + test('convert dotLottie version 1 to version 2 format', async () => { + const dotLottie = new DotLottieV1(); + + dotLottie.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottie.build(); + + const expectedManifest = { + animations: [{ id: 'bull' }], + author: PACKAGE_NAME, + generator: PACKAGE_NAME, + version: '1', + }; + + expect(dotLottie.manifest).toEqual(expectedManifest); + + const dotLottieFile = await dotLottie.toArrayBuffer(); + const unzipped = unzipSync(new Uint8Array(dotLottieFile)); + const filenames = Object.keys(unzipped); + + expect(filenames).toEqual([ + 'manifest.json', + 'animations/bull.json', + 'images/image_0.png', + 'images/image_1.png', + 'images/image_2.png', + 'images/image_3.png', + 'images/image_4.png', + ]); + + const dotLottieV2 = await toDotLottieV2(dotLottieFile); + + expect(dotLottieV2.manifest).toEqual({ + version: '2', + generator: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + + const dotLottieV2File = await dotLottieV2.toArrayBuffer(); + const unzippedV2 = unzipSync(new Uint8Array(dotLottieV2File)); + const filenamesV2 = Object.keys(unzippedV2); + + expect(filenamesV2).toEqual([ + 'manifest.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + ]); + + const assetMapping: Record = { + 'animations/bull.json': 'a/bull.json', + 'images/image_0.png': 'i/image_0.png', + 'images/image_1.png': 'i/image_1.png', + 'images/image_2.png': 'i/image_2.png', + 'images/image_3.png': 'i/image_3.png', + 'images/image_4.png': 'i/image_4.png', + }; + + for (const [v1Path, v2Path] of Object.entries(assetMapping)) { + if (v1Path.endsWith('.json')) { + const v1Content = unzipped[v1Path] ? JSON.parse(strFromU8(unzipped[v1Path] as Uint8Array)) : {}; + const v2Content = unzippedV2[v2Path] ? JSON.parse(strFromU8(unzippedV2[v2Path] as Uint8Array)) : {}; + + delete v1Content.assets; + delete v2Content.assets; + expect(v1Content).toEqual(v2Content); + } else { + const v1Content = unzipped[v1Path] ? strFromU8(unzipped[v1Path] as Uint8Array) : ''; + const v2Content = unzippedV2[v2Path] ? strFromU8(unzippedV2[v2Path] as Uint8Array) : ''; + + expect(v1Content).toMatch(v2Content); + } + } + }); +}); + +describe('toDotLottieV1', () => { + test('convert dotLottie version 2 to version 1 format', async () => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottie.build(); + + const dotLottieFile = await dotLottie.toArrayBuffer(); + const unzipped = unzipSync(new Uint8Array(dotLottieFile)); + const filenames = Object.keys(unzipped); + + expect(filenames).toEqual([ + 'manifest.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + ]); + + const dotLottieV1 = await toDotLottieV1(dotLottieFile); + + expect(dotLottieV1.manifest).toEqual({ + animations: [{ id: 'bull' }], + version: '1', + author: PACKAGE_NAME, + generator: PACKAGE_NAME, + }); + + const dotLottieV1File = await dotLottieV1.toArrayBuffer(); + const unzippedV1 = unzipSync(new Uint8Array(dotLottieV1File)); + const filenamesV1 = Object.keys(unzippedV1); + + expect(filenamesV1).toEqual([ + 'manifest.json', + 'animations/bull.json', + 'images/image_0.png', + 'images/image_1.png', + 'images/image_2.png', + 'images/image_3.png', + 'images/image_4.png', + ]); + + const assetMapping = { + 'a/bull.json': 'animations/bull.json', + 'i/image_0.png': 'images/image_0.png', + 'i/image_1.png': 'images/image_1.png', + 'i/image_2.png': 'images/image_2.png', + 'i/image_3.png': 'images/image_3.png', + 'i/image_4.png': 'images/image_4.png', + }; + + for (const [v2Path, v1Path] of Object.entries(assetMapping)) { + if (v2Path.endsWith('.json')) { + const v2Content = unzipped[v2Path] ? JSON.parse(strFromU8(unzipped[v2Path] as Uint8Array)) : {}; + const v1Content = unzippedV1[v1Path] ? JSON.parse(strFromU8(unzippedV1[v1Path] as Uint8Array)) : {}; + + delete v2Content.assets; + delete v1Content.assets; + expect(v2Content).toEqual(v1Content); + } else { + const v2Content = unzipped[v2Path] ? strFromU8(unzipped[v2Path] as Uint8Array) : ''; + const v1Content = unzippedV1[v1Path] ? strFromU8(unzippedV1[v1Path] as Uint8Array) : ''; + + expect(v2Content).toEqual(v1Content); + } + } + }); +}); + +describe('DotLottie v2', () => { + test('loads from a v1 dotLottie', async () => { + const dotLottieV1 = new DotLottieV1(); + + dotLottieV1.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottieV1.build(); + + const dotLottieV1File = await dotLottieV1.toArrayBuffer(); + + const dotLottieV2 = await new DotLottie().fromArrayBuffer(dotLottieV1File); + + expect(dotLottieV2.manifest).toEqual({ + version: '2', + generator: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + }); +}); + +describe('DotLottie v1', () => { + test('loads from a v2 dotLottie', async () => { + const dotLottieV2 = new DotLottie(); + + dotLottieV2.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottieV2.build(); + + const dotLottieV2File = await dotLottieV2.toArrayBuffer(); + + const dotLottieV1 = await new DotLottieV1().fromArrayBuffer(dotLottieV2File); + + expect(dotLottieV1.manifest).toEqual({ + version: '1', + generator: PACKAGE_NAME, + author: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + }); +}); diff --git a/packages/dotlottie-js/src/__tests__/node/index.spec.ts b/packages/dotlottie-js/src/__tests__/node/index.spec.ts new file mode 100644 index 00000000..1956eada --- /dev/null +++ b/packages/dotlottie-js/src/__tests__/node/index.spec.ts @@ -0,0 +1,224 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import type { Animation } from '@lottie-animation-community/lottie-types'; +import { strFromU8, unzipSync } from 'fflate'; +import { expect, test, describe } from 'vitest'; + +import { PACKAGE_NAME } from '../../constants'; +import { makeDotLottie } from '../../index.node'; +import { DotLottieV1, toDotLottieV1 } from '../../v1/node'; +import { DotLottie, toDotLottieV2 } from '../../v2/node'; +import bull from '../__fixtures__/bull.json'; + +const bullAnimationData = bull as Animation; + +describe('makeDotLottie', () => { + test('make dotLottie v1', () => { + const dotLottie = makeDotLottie('v1'); + + expect(dotLottie.version).toBe('1'); + + expect(dotLottie).toBeInstanceOf(DotLottieV1); + }); + + test('make dotLottie v2', () => { + const dotLottie = makeDotLottie('v2'); + + expect(dotLottie.version).toBe('2'); + + expect(dotLottie).toBeInstanceOf(DotLottie); + }); +}); + +describe('toDotLottieV2', () => { + test('convert dotLottie version 1 to version 2 format', async () => { + const dotLottie = new DotLottieV1(); + + dotLottie.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottie.build(); + + const expectedManifest = { + animations: [{ id: 'bull' }], + author: PACKAGE_NAME, + generator: PACKAGE_NAME, + version: '1', + }; + + expect(dotLottie.manifest).toEqual(expectedManifest); + + const dotLottieFile = await dotLottie.toArrayBuffer(); + const unzipped = unzipSync(new Uint8Array(dotLottieFile)); + const filenames = Object.keys(unzipped); + + expect(filenames).toEqual([ + 'manifest.json', + 'animations/bull.json', + 'images/image_0.png', + 'images/image_1.png', + 'images/image_2.png', + 'images/image_3.png', + 'images/image_4.png', + ]); + + const dotLottieV2 = await toDotLottieV2(dotLottieFile); + + expect(dotLottieV2.manifest).toEqual({ + version: '2', + generator: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + + const dotLottieV2File = await dotLottieV2.toArrayBuffer(); + const unzippedV2 = unzipSync(new Uint8Array(dotLottieV2File)); + const filenamesV2 = Object.keys(unzippedV2); + + expect(filenamesV2).toEqual([ + 'manifest.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + ]); + + const assetMapping: Record = { + 'animations/bull.json': 'a/bull.json', + 'images/image_0.png': 'i/image_0.png', + 'images/image_1.png': 'i/image_1.png', + 'images/image_2.png': 'i/image_2.png', + 'images/image_3.png': 'i/image_3.png', + 'images/image_4.png': 'i/image_4.png', + }; + + for (const [v1Path, v2Path] of Object.entries(assetMapping)) { + if (v1Path.endsWith('.json')) { + const v1Content = unzipped[v1Path] ? JSON.parse(strFromU8(unzipped[v1Path] as Uint8Array)) : {}; + const v2Content = unzippedV2[v2Path] ? JSON.parse(strFromU8(unzippedV2[v2Path] as Uint8Array)) : {}; + + delete v1Content.assets; + delete v2Content.assets; + expect(v1Content).toEqual(v2Content); + } else { + const v1Content = unzipped[v1Path] ? strFromU8(unzipped[v1Path] as Uint8Array) : ''; + const v2Content = unzippedV2[v2Path] ? strFromU8(unzippedV2[v2Path] as Uint8Array) : ''; + + expect(v1Content).toMatch(v2Content); + } + } + }); +}); + +describe('toDotLottieV1', () => { + test('convert dotLottie version 2 to version 1 format', async () => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottie.build(); + + const dotLottieFile = await dotLottie.toArrayBuffer(); + const unzipped = unzipSync(new Uint8Array(dotLottieFile)); + const filenames = Object.keys(unzipped); + + expect(filenames).toEqual([ + 'manifest.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + ]); + + const dotLottieV1 = await toDotLottieV1(dotLottieFile); + + expect(dotLottieV1.manifest).toEqual({ + animations: [{ id: 'bull' }], + version: '1', + author: PACKAGE_NAME, + generator: PACKAGE_NAME, + }); + + const dotLottieV1File = await dotLottieV1.toArrayBuffer(); + const unzippedV1 = unzipSync(new Uint8Array(dotLottieV1File)); + const filenamesV1 = Object.keys(unzippedV1); + + expect(filenamesV1).toEqual([ + 'manifest.json', + 'animations/bull.json', + 'images/image_0.png', + 'images/image_1.png', + 'images/image_2.png', + 'images/image_3.png', + 'images/image_4.png', + ]); + + const assetMapping = { + 'a/bull.json': 'animations/bull.json', + 'i/image_0.png': 'images/image_0.png', + 'i/image_1.png': 'images/image_1.png', + 'i/image_2.png': 'images/image_2.png', + 'i/image_3.png': 'images/image_3.png', + 'i/image_4.png': 'images/image_4.png', + }; + + for (const [v2Path, v1Path] of Object.entries(assetMapping)) { + if (v2Path.endsWith('.json')) { + const v2Content = unzipped[v2Path] ? JSON.parse(strFromU8(unzipped[v2Path] as Uint8Array)) : {}; + const v1Content = unzippedV1[v1Path] ? JSON.parse(strFromU8(unzippedV1[v1Path] as Uint8Array)) : {}; + + delete v2Content.assets; + delete v1Content.assets; + expect(v2Content).toEqual(v1Content); + } else { + const v2Content = unzipped[v2Path] ? strFromU8(unzipped[v2Path] as Uint8Array) : ''; + const v1Content = unzippedV1[v1Path] ? strFromU8(unzippedV1[v1Path] as Uint8Array) : ''; + + expect(v2Content).toEqual(v1Content); + } + } + }); +}); + +describe('DotLottie v2', () => { + test('loads from a v1 dotLottie', async () => { + const dotLottieV1 = new DotLottieV1(); + + dotLottieV1.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottieV1.build(); + + const dotLottieV1File = await dotLottieV1.toArrayBuffer(); + + const dotLottieV2 = await new DotLottie().fromArrayBuffer(dotLottieV1File); + + expect(dotLottieV2.manifest).toEqual({ + version: '2', + generator: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + }); +}); + +describe('DotLottie v1', () => { + test('loads from a v2 dotLottie', async () => { + const dotLottieV2 = new DotLottie(); + + dotLottieV2.addAnimation({ data: structuredClone(bullAnimationData), id: 'bull' }); + await dotLottieV2.build(); + + const dotLottieV2File = await dotLottieV2.toArrayBuffer(); + + const dotLottieV1 = await new DotLottieV1().fromArrayBuffer(dotLottieV2File); + + expect(dotLottieV1.manifest).toEqual({ + version: '1', + generator: PACKAGE_NAME, + author: PACKAGE_NAME, + animations: [{ id: 'bull' }], + }); + }); +}); diff --git a/packages/dotlottie-js/src/common/dotlottie-theme-common.ts b/packages/dotlottie-js/src/common/dotlottie-theme-common.ts deleted file mode 100644 index d30815fa..00000000 --- a/packages/dotlottie-js/src/common/dotlottie-theme-common.ts +++ /dev/null @@ -1,130 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -import type { ZipOptions } from 'fflate'; - -import type { LottieAnimationCommon } from './lottie-animation-common'; -import { createError, isValidURL } from './utils'; - -type Data = Record; - -export interface ThemeOptions { - data?: Data; - id: string; - url?: string; - zipOptions?: ZipOptions; -} - -export class LottieThemeCommon { - protected _data?: Data; - - protected _id: string = ''; - - protected _url?: string; - - protected readonly _animationsMap: Map = new Map(); - - protected _zipOptions: ZipOptions; - - public constructor(options: ThemeOptions) { - this._requireValidId(options.id); - this._id = options.id; - - if (options.data) { - this._requireValidData(options.data); - this._data = options.data; - } - - if (options.url) { - this._requireValidUrl(options.url); - this._url = options.url; - } - - this._zipOptions = options.zipOptions ?? {}; - } - - public get zipOptions(): ZipOptions { - return this._zipOptions; - } - - public set zipOptions(zipOptions: ZipOptions) { - this._zipOptions = zipOptions; - } - - public get id(): string { - return this._id; - } - - public set id(id: string | undefined) { - this._requireValidId(id); - - this._id = id; - } - - public get url(): string | undefined { - return this._url; - } - - public set url(url: string | undefined) { - this._requireValidUrl(url); - - this._url = url; - } - - public get data(): Data | undefined { - return this._data; - } - - public set data(data: Data | undefined) { - this._requireValidData(data); - - this._data = data; - } - - public get animations(): LottieAnimationCommon[] { - return Array.from(this._animationsMap.values()); - } - - public async toString(): Promise { - if (!this._data && this._url) { - await this._loadDataFromUrl(this._url); - } - - this._requireValidData(this._data); - - return JSON.stringify(this._data); - } - - public addAnimation(animation: LottieAnimationCommon): void { - this._animationsMap.set(animation.id, animation); - } - - public removeAnimation(animationId: string): void { - this._animationsMap.delete(animationId); - } - - private _requireValidId(id: string | undefined): asserts id is string { - if (typeof id !== 'string' || !id) throw createError('Invalid theme id'); - } - - private _requireValidUrl(url: string | undefined): asserts url is string { - if (!url || !isValidURL(url)) throw createError('Invalid theme url'); - } - - private _requireValidData(data: Data | undefined): asserts data is Data { - if (typeof data !== 'object') throw createError('Invalid theme data'); - } - - private async _loadDataFromUrl(url: string): Promise { - try { - const response = await fetch(url); - - const data = await response.json(); - - this._data = data; - } catch (error) { - throw createError(`Failed to fetch theme from url, Error: ${JSON.stringify(error)}`); - } - } -} diff --git a/packages/dotlottie-js/src/common/index.ts b/packages/dotlottie-js/src/common/index.ts deleted file mode 100644 index f802f444..00000000 --- a/packages/dotlottie-js/src/common/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -export * from './dotlottie-common'; -export * from './lottie-animation-common'; -export * from './lottie-image-common'; -export * from './dotlottie-plugin'; -export * from './duplicate-image-detector-common'; -export * from './utils'; -export * from './manifest'; -export * from './dotlottie-theme-common'; -export * from './dotlottie-state-machine-common'; -export * from './dotlottie-state'; -export * from './lottie-audio-common'; diff --git a/packages/dotlottie-js/src/constants.ts b/packages/dotlottie-js/src/constants.ts new file mode 100644 index 00000000..53d625c2 --- /dev/null +++ b/packages/dotlottie-js/src/constants.ts @@ -0,0 +1,7 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import pkg from '../package.json'; + +export const PACKAGE_NAME = `${pkg.name}@${pkg.version}`; diff --git a/packages/dotlottie-js/src/index.browser.ts b/packages/dotlottie-js/src/index.browser.ts new file mode 100644 index 00000000..570e8178 --- /dev/null +++ b/packages/dotlottie-js/src/index.browser.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import { DotLottieV1 } from './v1/browser'; +import type { DotLottieV1Options } from './v1/common'; +import { DotLottie } from './v2/browser'; +import type { DotLottieOptions } from './v2/common'; + +export function makeDotLottie( + version: T, + options?: T extends 'v1' ? DotLottieV1Options : DotLottieOptions, +): T extends 'v1' ? DotLottieV1 : DotLottie { + if (version === 'v1') { + return new DotLottieV1(options as DotLottieV1Options) as T extends 'v1' ? DotLottieV1 : DotLottie; + } + + return new DotLottie(options as DotLottieOptions) as T extends 'v1' ? DotLottieV1 : DotLottie; +} + +export * from './v1/browser'; +export * from './v1/common'; +export * from './v2/browser'; +export * from './v2/common'; +export * from './utils'; +export * from './types'; diff --git a/packages/dotlottie-js/src/index.node.ts b/packages/dotlottie-js/src/index.node.ts new file mode 100644 index 00000000..603624b5 --- /dev/null +++ b/packages/dotlottie-js/src/index.node.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import type { DotLottieV1Options } from './v1/common'; +import { DotLottieV1 } from './v1/node'; +import type { DotLottieOptions } from './v2/common'; +import { DotLottie } from './v2/node'; + +export function makeDotLottie( + version: T, + options?: T extends 'v1' ? DotLottieV1Options : DotLottieOptions, +): T extends 'v1' ? DotLottieV1 : DotLottie { + if (version === 'v1') { + return new DotLottieV1(options as DotLottieV1Options) as T extends 'v1' ? DotLottieV1 : DotLottie; + } + + return new DotLottie(options as DotLottieOptions) as T extends 'v1' ? DotLottieV1 : DotLottie; +} + +export * from './v1/node'; +export * from './v1/common'; +export * from './v2/node'; +export * from './v2/common'; +export * from './utils'; +export * from './types'; diff --git a/packages/dotlottie-js/src/index.ts b/packages/dotlottie-js/src/index.ts deleted file mode 100644 index 5be9da02..00000000 --- a/packages/dotlottie-js/src/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -export * from './dotlottie'; -export * from './lottie-animation'; -export * from './lottie-image'; -export * from './lottie-theme'; -export * from './common'; -export * from './lottie-state-machine'; -export * from './lottie-audio'; diff --git a/packages/dotlottie-js/src/node/index.ts b/packages/dotlottie-js/src/node/index.ts deleted file mode 100644 index fb30c2ed..00000000 --- a/packages/dotlottie-js/src/node/index.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -export * from './dotlottie'; -export * from './lottie-animation'; -export * from './lottie-image'; -export * from './lottie-theme'; -export * from '../common'; -export * from './lottie-state-machine'; diff --git a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/themes/theme1.json b/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/themes/theme1.json deleted file mode 100644 index c03d061b..00000000 --- a/packages/dotlottie-js/src/tests/__fixtures__/simple/animation/themes/theme1.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "face_color": { - "p": { - "a": 0, - "k": [ - 1, - 0, - 0, - 1 - ], - "ix": 4 - } - } -} \ No newline at end of file diff --git a/packages/dotlottie-js/src/tests/dotlottie-js-browser.spec.ts b/packages/dotlottie-js/src/tests/dotlottie-js-browser.spec.ts deleted file mode 100644 index ce184af9..00000000 --- a/packages/dotlottie-js/src/tests/dotlottie-js-browser.spec.ts +++ /dev/null @@ -1,1411 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable max-classes-per-file */ - -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import { Base64 } from 'js-base64'; - -import { DotLottie, LottieAnimation } from '..'; -import pkg from '../../package.json'; -import type { AnimationData, AnimationOptions, Manifest, ManifestAnimation } from '../common'; -import { LottieThemeCommon, PlayMode, DotLottiePlugin } from '../common'; - -import bullData from './__fixtures__/image-asset-optimization/bull.json'; -import IMAGE_ANIMATION_1_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-1.json'; -import IMAGE_ANIMATION_5_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; -import IMAGE_ANIMATION_4_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; -import IMAGE_ANIMATION_3_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; -import IMAGE_ANIMATION_2_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2.json'; -import SIMPLE_IMAGE_ANIMATION from './__fixtures__/image-asset-optimization/simple-image-animation.json'; -import dotlottieAnimation from './__fixtures__/simple/animation.lottie'; -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; -import manifest from './__fixtures__/simple/animation/manifest.json'; -import themeData from './__fixtures__/simple/animation/themes/theme1.json'; -import bigMergedDotLottie from './__fixtures__/simple/big-merged-dotlottie.lottie'; -import editedDotlottieAnimation from './__fixtures__/simple/edited-settings.lottie'; -import editedAnimationData from './__fixtures__/simple/edited-settings/animations/lottie01.json'; -import editedManifest from './__fixtures__/simple/edited-settings/manifest.json'; -import { customMatchers } from './test-utils'; - -describe('DotLottie', () => { - beforeAll(() => { - jasmine.addMatchers(customMatchers); - }); - - describe('setVersion', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setVersion('1.0.0'); - - expect(result).toBe(dotlottie); - }); - - it('sets the version', () => { - const dotlottie = new DotLottie(); - - const version = '1.0.0'; - - dotlottie.setVersion(version); - - expect(dotlottie.version).toBe(version); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setVersion(''); - - expect(dotlottie.version).toBe(''); - }); - }); - - describe('setAuthor', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setAuthor('Design Barn'); - - expect(result).toBe(dotlottie); - }); - - it('sets the author', () => { - const dotlottie = new DotLottie(); - - dotlottie.setAuthor('Design Barn'); - - expect(dotlottie.author).toBe('Design Barn'); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setAuthor(''); - - expect(dotlottie.author).toBe(''); - }); - }); - - describe('setRevision', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setRevision(1); - - expect(result).toBe(dotlottie); - }); - - it('sets the revision', () => { - const dotlottie = new DotLottie(); - - const revision = 1.5; - - dotlottie.setRevision(revision); - - expect(dotlottie.revision).toBe(revision); - }); - }); - - describe('setDescription', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setDescription('A description'); - - expect(result).toBe(dotlottie); - }); - - it('sets the description', () => { - const dotlottie = new DotLottie(); - - dotlottie.setDescription('A description'); - - expect(dotlottie.description).toBe('A description'); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setDescription(''); - - expect(dotlottie.description).toBe(''); - }); - }); - - describe('setGenerator', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setGenerator('Design Barn'); - - expect(result).toBe(dotlottie); - }); - - it('sets the generator', () => { - const dotlottie = new DotLottie(); - - const generator = 'Design Barn'; - - dotlottie.setGenerator(generator); - - expect(dotlottie.generator).toBe(generator); - }); - - it('has proper generator when no input provided', () => { - const dotLottie = new DotLottie(); - - expect(dotLottie.generator).toBe(`${pkg.name}@${pkg.version}`); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setGenerator(''); - - expect(dotlottie.generator).toBe(''); - }); - }); - - describe('setRevision', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setRevision(1); - - expect(result).toBe(dotlottie); - }); - - it('sets the revision', () => { - const dotlottie = new DotLottie(); - - const revision = 1.5; - - dotlottie.setRevision(revision); - - expect(dotlottie.revision).toBe(revision); - }); - }); - - describe('setKeywords', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setKeywords('animation, design, lottie'); - - expect(result).toBe(dotlottie); - }); - - it('sets the keywords', () => { - const dotlottie = new DotLottie(); - - const keywords = 'animation, design, lottie'; - - dotlottie.setKeywords(keywords); - - expect(dotlottie.keywords).toBe(keywords); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setKeywords(''); - - expect(dotlottie.keywords).toBe(''); - }); - }); - - describe('addAnimation', () => { - it('throws an error if it receives a duplicate id when constructed', () => { - expect(() => { - const dotLottie = new DotLottie(); - - dotLottie.addAnimation({ - id: 'test', - url: 'https://example.com/test.lottie', - }); - dotLottie.addAnimation({ - id: 'test', - url: 'https://example.com/test.lottie', - }); - }).toThrowError('[dotlottie-js]: Duplicate animation id detected, aborting.'); - }); - - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(result).toBe(dotlottie); - }); - - it('adds an animation', () => { - const animationId = manifest.animations[0]?.id as string; - - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: animationId, - data: animationData as unknown as AnimationType, - }); - - expect(dotlottie.animations.length).toBe(1); - - const animation = dotlottie.animations[0]; - - expect(animation?.id).toBe(manifest.animations[0]?.id); - }); - - it('adds an animation using all customizable options', async () => { - const animationId = 'test_animation'; - - const dotlottie = new DotLottie(); - - const animationOptions: AnimationOptions = { - id: animationId, - data: animationData as unknown as AnimationType, - autoplay: true, - direction: -1, - hover: true, - intermission: 1000, - loop: true, - playMode: PlayMode.Bounce, - speed: 1.5, - }; - - const manifestDataToCompare: ManifestAnimation = { - id: animationId, - autoplay: true, - direction: -1, - hover: true, - intermission: 1000, - loop: true, - playMode: PlayMode.Bounce, - speed: 1.5, - }; - - dotlottie.addAnimation({ - ...animationOptions, - }); - - await dotlottie.build(); - - const animationManifest = dotlottie.manifest.animations[0]; - - expect(animationManifest).toEqual(manifestDataToCompare); - }); - }); - - describe('removeAnimation', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(result).toBe(dotlottie); - }); - - it('removes an animation', () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(dotlottie.animations.length).toBe(1); - - dotlottie.removeAnimation(manifest.animations[0]?.id as string); - - expect(dotlottie.animations.length).toBe(0); - }); - }); - - describe('getAnimation', () => { - it('returns animation instance', async () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); - - expect(animation).toBeInstanceOf(LottieAnimation); - - expect(animation?.id).toBe(manifest.animations[0]?.id); - expect(animation?.data).toEqual(animationData as unknown as AnimationType); - }); - - it('returns undefined if the animation does not exist', async () => { - const dotlottie = new DotLottie(); - - const animation = await dotlottie.getAnimation('non_existent_animation'); - - expect(animation).toBeUndefined(); - }); - - it('returns animation instance with inlined assets', async () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: structuredClone(bullData) as unknown as AnimationData, - }); - - const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); - - expect(animation).toBeInstanceOf(LottieAnimation); - expect(animation?.id).toBe(manifest.animations[0]?.id); - expect(animation?.data).toEqual(bullData as unknown as AnimationData); - }); - - it('adds multiple animations and verifies their inlined assets', async () => { - const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); - - dotlottie - .addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, - }); - - const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); - const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); - const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); - const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); - const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); - const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); - - expect(animationV1).toBeInstanceOf(LottieAnimation); - expect(animationV1?.id).toBe('v1'); - expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); - - expect(animationV2).toBeInstanceOf(LottieAnimation); - expect(animationV2?.id).toBe('v2'); - expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); - - expect(animationV3).toBeInstanceOf(LottieAnimation); - expect(animationV3?.id).toBe('v3'); - expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); - - expect(animationV4).toBeInstanceOf(LottieAnimation); - expect(animationV4?.id).toBe('v4'); - expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); - - expect(animationV5).toBeInstanceOf(LottieAnimation); - expect(animationV5?.id).toBe('v5'); - expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); - - expect(animationV6).toBeInstanceOf(LottieAnimation); - expect(animationV6?.id).toBe('v6'); - expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); - }); - - it('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { - const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); - - await dotlottie - .addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), - }) - .build(); - - const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); - const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); - const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); - const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); - const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); - const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); - - expect(animationV1).toBeInstanceOf(LottieAnimation); - expect(animationV1?.id).toBe('v1'); - expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); - - expect(animationV2).toBeInstanceOf(LottieAnimation); - expect(animationV2?.id).toBe('v2'); - expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); - - expect(animationV3).toBeInstanceOf(LottieAnimation); - expect(animationV3?.id).toBe('v3'); - expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); - - expect(animationV4).toBeInstanceOf(LottieAnimation); - expect(animationV4?.id).toBe('v4'); - expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); - - expect(animationV5).toBeInstanceOf(LottieAnimation); - expect(animationV5?.id).toBe('v5'); - expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); - - expect(animationV6).toBeInstanceOf(LottieAnimation); - expect(animationV6?.id).toBe('v6'); - expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); - }); - }); - - describe('download', () => { - it('throws error on node environment', async () => { - // skip test if running in browser environment - if (typeof window !== 'undefined') return; - - const dotlottie = new DotLottie(); - - await expectAsync( - dotlottie - .addAnimation({ - id: 'test_animation', - data: animationData as unknown as AnimationType, - }) - .download('file'), - ).toBeRejectedWithError('[dotlottie-js]: Cannot download dotlottie in a non-browser environment'); - }); - - it('downloads dotlottie file on browser', async () => { - // skip test if running in node environment - if (typeof window === 'undefined') return; - - const dotlottie = new DotLottie(); - - const fileName = 'test.lottie'; - - const fakeLink = document.createElement('a'); - - const clickSpy = spyOn(fakeLink, 'click').and.callFake(() => { - // do nothing - }); - - spyOn(document, 'createElement').and.callFake(() => { - return fakeLink; - }); - - const createObjectURLSpy = spyOn(URL, 'createObjectURL').and.callThrough(); - - await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme1', - data: themeData, - }) - .assignTheme({ - animationId: 'lottie1', - themeId: 'theme1', - }) - .build(); - - await dotlottie.download(fileName); - - const blob = await dotlottie.toBlob(); - - expect(createObjectURLSpy).toHaveBeenCalledTimes(1); - expect(createObjectURLSpy).toHaveBeenCalledWith(blob); - - expect(fakeLink.download).toBe(fileName); - expect(fakeLink.style.display).toBe('none'); - expect(clickSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('toBlob', () => { - it('returns a blob', async () => { - const dotlottie = new DotLottie(); - - const blob = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - const arrayBuffer = await blob.arrayBuffer(); - - expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotlottie = new DotLottie(); - - const blob1 = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBlob({ - zipOptions: { - level: 9, - }, - }); - - const blob2 = await dotlottie.toBlob({ - zipOptions: { - level: 0, - }, - }); - - expect(blob1).toBeInstanceOf(Blob); - expect(blob2).toBeInstanceOf(Blob); - - const arrayBuffer1 = await blob1.arrayBuffer(); - const arrayBuffer2 = await blob2.arrayBuffer(); - - expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); - }); - }); - - describe('toArrayBuffer', () => { - it('returns an array buffer', async () => { - const dotlottie = new DotLottie(); - - const arrayBuffer = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotLottie1 = new DotLottie(); - - const arrayBuffer1 = await dotLottie1 - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toArrayBuffer({ - zipOptions: { - level: 9, - }, - }); - - const arrayBuffer2 = await dotLottie1.toArrayBuffer({ - zipOptions: { - level: 0, - }, - }); - - expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); - }); - }); - - describe('toBase64', () => { - it('returns base64 string', async () => { - const dotlottie = new DotLottie(); - - const dataURL = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBase64(); - - const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; - - expect(actualArrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotLottie1 = new DotLottie(); - - const dataURL1 = await dotLottie1 - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBase64({ - zipOptions: { - level: 9, - }, - }); - - const dataURL2 = await dotLottie1.toBase64({ - zipOptions: { - level: 0, - }, - }); - - const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; - const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; - - expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); - }); - }); - - describe('fromURL', () => { - it('throws an error if the URL is invalid', async () => { - const dotLottie = new DotLottie(); - - await expectAsync(dotLottie.fromURL('invalid-url')).toBeRejectedWithError('[dotlottie-js]: Invalid URL'); - }); - - it('loads a dotlottie file from a URL', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotlottieAnimation)), - ); - - const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await new DotLottie().fromURL(animationURL); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - expect(dotLottie.animations.length).toBe(1); - expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); - expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - expect(dotLottie.manifest).toEqual(manifest as Manifest); - expect(dotLottie.themes.length).toBe(1); - expect(dotLottie.themes[0]?.id).toEqual(manifest.themes[0]?.id); - expect(dotLottie.themes[0]?.data).toEqual(themeData); - }); - - it('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(editedDotlottieAnimation)), - ); - - const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - expect(dotlottie.animations.length).toBe(1); - expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); - expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); - expect(dotlottie.manifest).toEqual(editedManifest as Manifest); - }); - }); - - describe('fromArrayBuffer', () => { - it('loads a dotlottie file from an array buffer', async () => { - const arrayBuffer = dotlottieAnimation; - - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); - - expect(dotlottie.animations.length).toBe(1); - expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); - expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - expect(dotlottie.manifest).toEqual(manifest as Manifest); - expect(dotlottie.themes.length).toBe(1); - expect(dotlottie.themes[0]?.id).toEqual(manifest.themes[0]?.id); - expect(dotlottie.themes[0]?.data).toEqual(themeData); - }); - - it('loads a dotLottie containing images from an array buffer', async () => { - const arrayBuffer = bigMergedDotLottie; - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); - - expect(dotlottie.animations.length).toBe(6); - expect(dotlottie.getImages().length).toBe(16); - expect(dotlottie.animations[0]?.id).toEqual('v1'); - expect(dotlottie.animations[1]?.id).toEqual('v2'); - expect(dotlottie.animations[2]?.id).toEqual('v3'); - expect(dotlottie.animations[3]?.id).toEqual('v4'); - expect(dotlottie.animations[4]?.id).toEqual('v5'); - expect(dotlottie.animations[5]?.id).toEqual('v6'); - expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); - }); - }); - - describe('addPlugins', () => { - it('adds plugins to the animation', () => { - class TestPlugin extends DotLottiePlugin { - public readonly name = 'test'; - - public override async onBuild(): Promise { - return Promise.resolve(); - } - } - - const dotlottie = new DotLottie(); - - const plugin = new TestPlugin(); - - dotlottie.addPlugins(plugin); - - expect(dotlottie.plugins).toContain(plugin); - }); - }); - - describe('removePlugins', () => { - it('removes plugins from the animation', () => { - class TestPlugin extends DotLottiePlugin { - public readonly name = 'test'; - - public override async onBuild(): Promise { - return Promise.resolve(); - } - } - - const dotlottie = new DotLottie(); - - const plugin = new TestPlugin(); - - dotlottie.addPlugins(plugin); - - expect(dotlottie.plugins).toContain(plugin); - - dotlottie.removePlugins(plugin); - - expect(dotlottie.plugins).not.toContain(plugin); - }); - }); - - describe('imageAssets', () => { - it('Adds the Bull animation and checks number of images.', async () => { - const dotlottie = await new DotLottie() - .addAnimation({ - id: 'animation_1', - data: structuredClone(bullData) as unknown as AnimationData, - }) - .build(); - - const animation1 = await dotlottie.getAnimation('animation_1'); - - expect(animation1?.imageAssets.length).toBe(5); - }); - }); - - describe('merge', () => { - it('merges two dotlottie files', async () => { - const dotlottie1 = new DotLottie().addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }); - - const dotlottie2 = new DotLottie().addAnimation({ - id: 'lottie2', - data: animationData as unknown as AnimationType, - }); - - const dotlottie3 = new DotLottie().addAnimation({ - id: 'lottie3', - data: structuredClone(bullData as unknown as AnimationData), - }); - - const dotlottie4 = new DotLottie().addAnimation({ - id: 'lottie4', - data: structuredClone(bullData as unknown as AnimationData), - }); - - const shrekVariant1 = new DotLottie().addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), - }); - - const shrekVariant2 = new DotLottie().addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), - }); - - const shrekVariant3 = new DotLottie().addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), - }); - - const shrekVariant4 = new DotLottie().addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), - }); - - const shrekVariant5 = new DotLottie().addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), - }); - - const shrekVariant6 = new DotLottie().addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), - }); - - const [mergedImageLottie] = await Promise.all([ - new DotLottie().merge(dotlottie3, dotlottie4), - new DotLottie().merge(dotlottie3, dotlottie4).build(), - ]); - - const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ - new DotLottie() - .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) - .build(), - new DotLottie().merge(dotlottie1, dotlottie2).build(), - ]); - - expect(mergedImageLottie.animations.length).toBe(2); - - expect(bigMergedImageLottie.animations.length).toBe(6); - - expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ - 'v1', - 'v2', - 'v3', - 'v4', - 'v5', - 'v6', - ]); - - expect(mergedDotlottie).toBeInstanceOf(DotLottie); - - expect(mergedDotlottie.animations.length).toBe(2); - - expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); - expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - - expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); - expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); - }); - }); - - describe('build', () => { - it('it resolves lottie animations', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation.json'; - - const dotlottie = new DotLottie().addAnimation({ - url: animationURL, - id: 'lottie1', - }); - - expect(dotlottie.animations[0]?.data).toBeUndefined(); - - await dotlottie.build(); - - expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - }); - - it('runs plugins in correct order', async () => { - const parallel1OnBuild = jasmine.createSpy('parallel1OnBuild'); - const parallel2OnBuild = jasmine.createSpy('parallel2OnBuild'); - - const sequential1OnBuild = jasmine.createSpy('sequential1OnBuild'); - const sequential2OnBuild = jasmine.createSpy('sequential2OnBuild'); - - class Parallel1TestPlugin extends DotLottiePlugin { - public readonly name = 'parallel-test'; - - public constructor() { - super({ parallel: true }); - } - - public override async onBuild(): Promise { - parallel1OnBuild(this.dotlottie); - } - } - class Parallel2TestPlugin extends DotLottiePlugin { - public readonly name = 'parallel-test'; - - public constructor() { - super({ parallel: true }); - } - - public override async onBuild(): Promise { - parallel2OnBuild(this.dotlottie); - } - } - - class Sequential1TestPlugin extends DotLottiePlugin { - public readonly name = 'sequential-test'; - - public override async onBuild(): Promise { - sequential1OnBuild(this.dotlottie); - } - } - - class Sequential2TestPlugin extends DotLottiePlugin { - public readonly name = 'sequential-test'; - - public override async onBuild(): Promise { - sequential2OnBuild(this.dotlottie); - } - } - - const dotlottie = new DotLottie() - .addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }) - .addPlugins( - new Sequential1TestPlugin(), - new Parallel1TestPlugin(), - new Parallel2TestPlugin(), - new Sequential2TestPlugin(), - ); - - expect(parallel1OnBuild).not.toHaveBeenCalled(); - expect(parallel2OnBuild).not.toHaveBeenCalled(); - expect(sequential1OnBuild).not.toHaveBeenCalled(); - expect(sequential2OnBuild).not.toHaveBeenCalled(); - - await dotlottie.build(); - - expect(parallel1OnBuild).toHaveBeenCalledTimes(1); - expect(parallel2OnBuild).toHaveBeenCalledTimes(1); - expect(sequential1OnBuild).toHaveBeenCalledTimes(1); - expect(sequential2OnBuild).toHaveBeenCalledTimes(1); - - expect(parallel1OnBuild).toHaveBeenCalledWith(dotlottie); - expect(parallel2OnBuild).toHaveBeenCalledWith(dotlottie); - expect(sequential1OnBuild).toHaveBeenCalledWith(dotlottie); - expect(sequential2OnBuild).toHaveBeenCalledWith(dotlottie); - - expect(parallel1OnBuild).toHaveBeenCalledBefore(parallel2OnBuild); - expect(parallel2OnBuild).toHaveBeenCalledBefore(sequential1OnBuild); - expect(sequential1OnBuild).toHaveBeenCalledBefore(sequential2OnBuild); - }); - - describe('addTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - // act - const result = dotlottie.addTheme(theme); - - // assert - expect(result).toBe(dotlottie); - }); - - it('adds theme', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme1 = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const theme2 = { - id: 'theme_2', - url: 'https://fake.lottiefiles.com/theme.json', - }; - - // act - dotlottie.addTheme(theme1).addTheme(theme2); - - const themes = dotlottie.themes; - - // assert - expect(themes.length).toBe(2); - - expect(themes[0]).toBeInstanceOf(LottieThemeCommon); - expect(themes[0]?.id).toBe(theme1.id); - expect(themes[0]?.data).toBe(theme1.data); - expect(themes[0]?.url).toBeUndefined(); - - expect(themes[1]).toBeInstanceOf(LottieThemeCommon); - expect(themes[1]?.id).toBe(theme2.id); - expect(themes[1]?.url).toBe(theme2.url); - expect(themes[1]?.data).toBeUndefined(); - }); - }); - - describe('removeTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - // act - const result = dotlottie.addTheme(theme).removeTheme(theme.id); - - // assert - expect(result).toBe(dotlottie); - }); - - it('removes theme', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme1 = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const theme2 = { - id: 'theme_2', - url: 'https://fake.lottiefiles.com/theme.json', - }; - - // act - dotlottie.addTheme(theme1).addTheme(theme2).removeTheme(theme1.id); - - // assert - expect(dotlottie.themes.length).toBe(1); - expect(dotlottie.themes[0]?.id).toBe(theme2.id); - }); - }); - - describe('getTheme', () => { - it('returns theme by id', () => { - // arrange - const dotLottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - dotLottie.addTheme(theme); - - // act - const result = dotLottie.getTheme(theme.id); - - // assert - expect(result).toBeInstanceOf(LottieThemeCommon); - expect(result?.id).toBe(theme.id); - expect(result?.data).toBe(theme.data); - }); - - it('returns undefined if theme does not exist', () => { - // arrange - const dotLottie = new DotLottie(); - - // act - const result = dotLottie.getTheme('theme_1'); - - // assert - expect(result).toBeUndefined(); - }); - }); - - describe('assignTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - // act - const result = dotlottie - .addAnimation({ - id: 'animation_1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }) - .assignTheme({ - themeId: 'theme_1', - animationId: 'animation_1', - }); - - // assert - expect(result).toBe(dotlottie); - }); - - it('throws error if animation does not exist', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const animation = { - id: 'animation_1', - data: animationData as unknown as AnimationType, - }; - - expect(() => { - // act - dotlottie.addTheme(theme).assignTheme({ - themeId: theme.id, - animationId: animation.id, - }); - // assert - }).toThrowError(`[dotlottie-js]: Failed to find animation with id ${animation.id}`); - }); - - it('throws error if theme does not exist', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const animation = { - id: 'animation_1', - data: animationData as unknown as AnimationType, - }; - - expect(() => { - // act - dotlottie.addAnimation(animation).assignTheme({ - themeId: theme.id, - animationId: animation.id, - }); - // assert - }).toThrowError(`[dotlottie-js]: Failed to find theme with id ${theme.id}`); - }); - - it('assigns an existing theme to an existing animation', async () => { - // arrange - const dotlottie = new DotLottie(); - - // act - dotlottie - .addAnimation({ - id: 'animation_1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }) - .assignTheme({ - themeId: 'theme_1', - animationId: 'animation_1', - }); - - // assert - const assignedTheme = dotlottie.getTheme('theme_1'); - const animation = await dotlottie.getAnimation('animation_1'); - - expect(assignedTheme?.animations.length).toBe(1); - expect(assignedTheme?.animations[0]).toBe(animation); - - expect(animation?.themes.length).toBe(1); - expect(animation?.themes[0]).toBe(assignedTheme); - - expect(dotlottie.manifest.themes).toEqual([ - { - id: 'theme_1', - animations: ['animation_1'], - }, - ]); - }); - }); - }); -}); diff --git a/packages/dotlottie-js/src/tests/dotlottie-js-node.spec.ts b/packages/dotlottie-js/src/tests/dotlottie-js-node.spec.ts deleted file mode 100644 index 4f79336c..00000000 --- a/packages/dotlottie-js/src/tests/dotlottie-js-node.spec.ts +++ /dev/null @@ -1,1427 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable max-classes-per-file */ -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import { unzipSync } from 'fflate'; -import { Base64 } from 'js-base64'; - -import pkg from '../../package.json'; -import type { AnimationData, AnimationOptions, Manifest, ManifestAnimation } from '../common'; -import { LottieThemeCommon, PlayMode, DotLottiePlugin } from '../common'; -import { DotLottie, LottieAnimation } from '../node'; - -import bullData from './__fixtures__/image-asset-optimization/bull.json'; -import IMAGE_ANIMATION_1_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-1.json'; -import IMAGE_ANIMATION_5_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; -import IMAGE_ANIMATION_4_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; -import IMAGE_ANIMATION_3_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; -import IMAGE_ANIMATION_2_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2.json'; -import SIMPLE_IMAGE_ANIMATION from './__fixtures__/image-asset-optimization/simple-image-animation.json'; -import dotlottieAnimation from './__fixtures__/simple/animation.lottie'; -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; -import manifest from './__fixtures__/simple/animation/manifest.json'; -import themeData from './__fixtures__/simple/animation/themes/theme1.json'; -import bigMergedDotLottie from './__fixtures__/simple/big-merged-dotlottie.lottie'; -import editedDotlottieAnimation from './__fixtures__/simple/edited-settings.lottie'; -import editedAnimationData from './__fixtures__/simple/edited-settings/animations/lottie01.json'; -import editedManifest from './__fixtures__/simple/edited-settings/manifest.json'; -import { customMatchers } from './test-utils'; - -describe('DotLottie', () => { - beforeAll(() => { - jasmine.addMatchers(customMatchers); - }); - - describe('setVersion', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setVersion('1.0.0'); - - expect(result).toBe(dotlottie); - }); - - it('sets the version', () => { - const dotlottie = new DotLottie(); - - const version = '1.0.0'; - - dotlottie.setVersion(version); - - expect(dotlottie.version).toBe(version); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setVersion(''); - - expect(dotlottie.version).toBe(''); - }); - }); - - describe('setAuthor', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setAuthor('Design Barn'); - - expect(result).toBe(dotlottie); - }); - - it('sets the author', () => { - const dotlottie = new DotLottie(); - - dotlottie.setAuthor('Design Barn'); - - expect(dotlottie.author).toBe('Design Barn'); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setAuthor(''); - - expect(dotlottie.author).toBe(''); - }); - }); - - describe('setRevision', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setRevision(1); - - expect(result).toBe(dotlottie); - }); - - it('sets the revision', () => { - const dotlottie = new DotLottie(); - - const revision = 1.5; - - dotlottie.setRevision(revision); - - expect(dotlottie.revision).toBe(revision); - }); - }); - - describe('setDescription', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setDescription('A description'); - - expect(result).toBe(dotlottie); - }); - - it('sets the description', () => { - const dotlottie = new DotLottie(); - - dotlottie.setDescription('A description'); - - expect(dotlottie.description).toBe('A description'); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setDescription(''); - - expect(dotlottie.description).toBe(''); - }); - }); - - describe('setGenerator', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setGenerator('Design Barn'); - - expect(result).toBe(dotlottie); - }); - - it('sets the generator', () => { - const dotlottie = new DotLottie(); - - const generator = 'Design Barn'; - - dotlottie.setGenerator(generator); - - expect(dotlottie.generator).toBe(generator); - }); - - it('has proper generator when no input provided', () => { - const dotLottie = new DotLottie(); - - expect(dotLottie.generator).toBe(`${pkg.name}/node@${pkg.version}`); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setGenerator(''); - - expect(dotlottie.generator).toBe(''); - }); - }); - - describe('setRevision', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setRevision(1); - - expect(result).toBe(dotlottie); - }); - - it('sets the revision', () => { - const dotlottie = new DotLottie(); - - const revision = 1.5; - - dotlottie.setRevision(revision); - - expect(dotlottie.revision).toBe(revision); - }); - }); - - describe('setKeywords', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.setKeywords('animation, design, lottie'); - - expect(result).toBe(dotlottie); - }); - - it('sets the keywords', () => { - const dotlottie = new DotLottie(); - - const keywords = 'animation, design, lottie'; - - dotlottie.setKeywords(keywords); - - expect(dotlottie.keywords).toBe(keywords); - }); - - it('accepts empty string', () => { - const dotlottie = new DotLottie(); - - dotlottie.setKeywords(''); - - expect(dotlottie.keywords).toBe(''); - }); - }); - - describe('addAnimation', () => { - it('throws an error if it receives a duplicate id when constructed', () => { - expect(() => { - const dotLottie = new DotLottie(); - - dotLottie.addAnimation({ - id: 'test', - url: 'https://example.com/test.lottie', - }); - dotLottie.addAnimation({ - id: 'test', - url: 'https://example.com/test.lottie', - }); - }).toThrowError('[dotlottie-js]: Duplicate animation id detected, aborting.'); - }); - - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(result).toBe(dotlottie); - }); - - it('adds an animation', () => { - const animationId = manifest.animations[0]?.id as string; - - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: animationId, - data: animationData as unknown as AnimationType, - }); - - expect(dotlottie.animations.length).toBe(1); - - const animation = dotlottie.animations[0]; - - expect(animation?.id).toBe(manifest.animations[0]?.id); - }); - - it('adds an animation using all customizable options', async () => { - const animationId = 'test_animation'; - - const dotlottie = new DotLottie(); - - const animationOptions: AnimationOptions = { - id: animationId, - data: animationData as unknown as AnimationType, - autoplay: true, - direction: -1, - hover: true, - intermission: 1000, - loop: true, - playMode: PlayMode.Bounce, - speed: 1.5, - }; - - const manifestDataToCompare: ManifestAnimation = { - id: animationId, - autoplay: true, - direction: -1, - hover: true, - intermission: 1000, - loop: true, - playMode: PlayMode.Bounce, - speed: 1.5, - }; - - dotlottie.addAnimation({ - ...animationOptions, - }); - - await dotlottie.build(); - - const animationManifest = dotlottie.manifest.animations[0]; - - expect(animationManifest).toEqual(manifestDataToCompare); - }); - }); - - describe('removeAnimation', () => { - it('returns the dotlottie instance', () => { - const dotlottie = new DotLottie(); - - const result = dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(result).toBe(dotlottie); - }); - - it('removes an animation', () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - expect(dotlottie.animations.length).toBe(1); - - dotlottie.removeAnimation(manifest.animations[0]?.id as string); - - expect(dotlottie.animations.length).toBe(0); - }); - }); - - describe('getAnimation', () => { - it('returns animation instance', async () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }); - - const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); - - expect(animation).toBeInstanceOf(LottieAnimation); - - expect(animation?.id).toBe(manifest.animations[0]?.id); - expect(animation?.data).toEqual(animationData as unknown as AnimationType); - }); - - it('returns undefined if the animation does not exist', async () => { - const dotlottie = new DotLottie(); - - const animation = await dotlottie.getAnimation('non_existent_animation'); - - expect(animation).toBeUndefined(); - }); - - it('returns animation instance with inlined assets', async () => { - const dotlottie = new DotLottie(); - - dotlottie.addAnimation({ - id: manifest.animations[0]?.id as string, - data: structuredClone(bullData) as unknown as AnimationData, - }); - - const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); - - expect(animation).toBeInstanceOf(LottieAnimation); - expect(animation?.id).toBe(manifest.animations[0]?.id); - expect(animation?.data).toEqual(bullData as unknown as AnimationData); - }); - - it('adds multiple animations and verifies their inlined assets', async () => { - const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); - - dotlottie - .addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, - }) - .addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, - }); - - const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); - const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); - const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); - const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); - const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); - const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); - - expect(animationV1).toBeInstanceOf(LottieAnimation); - expect(animationV1?.id).toBe('v1'); - expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); - - expect(animationV2).toBeInstanceOf(LottieAnimation); - expect(animationV2?.id).toBe('v2'); - expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); - - expect(animationV3).toBeInstanceOf(LottieAnimation); - expect(animationV3?.id).toBe('v3'); - expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); - - expect(animationV4).toBeInstanceOf(LottieAnimation); - expect(animationV4?.id).toBe('v4'); - expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); - - expect(animationV5).toBeInstanceOf(LottieAnimation); - expect(animationV5?.id).toBe('v5'); - expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); - - expect(animationV6).toBeInstanceOf(LottieAnimation); - expect(animationV6?.id).toBe('v6'); - expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); - }); - - it('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { - const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); - - await dotlottie - .addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), - }) - .addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), - }) - .build(); - - const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); - const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); - const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); - const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); - const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); - const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); - - expect(animationV1).toBeInstanceOf(LottieAnimation); - expect(animationV1?.id).toBe('v1'); - expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); - - expect(animationV2).toBeInstanceOf(LottieAnimation); - expect(animationV2?.id).toBe('v2'); - expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); - - expect(animationV3).toBeInstanceOf(LottieAnimation); - expect(animationV3?.id).toBe('v3'); - expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); - - expect(animationV4).toBeInstanceOf(LottieAnimation); - expect(animationV4?.id).toBe('v4'); - expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); - - expect(animationV5).toBeInstanceOf(LottieAnimation); - expect(animationV5?.id).toBe('v5'); - expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); - - expect(animationV6).toBeInstanceOf(LottieAnimation); - expect(animationV6?.id).toBe('v6'); - expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); - }); - }); - - describe('download', () => { - it('throws error on node environment', async () => { - // skip test if running in browser environment - if (typeof window !== 'undefined') return; - - const dotlottie = new DotLottie(); - - await expectAsync( - dotlottie - .addAnimation({ - id: 'test_animation', - data: animationData as unknown as AnimationType, - }) - .download('file'), - ).toBeRejectedWithError('[dotlottie-js]: Cannot download dotlottie in a non-browser environment'); - }); - - it('downloads dotlottie file on browser', async () => { - // skip test if running in node environment - if (typeof window === 'undefined') return; - - const dotlottie = new DotLottie(); - - const fileName = 'test.lottie'; - - const fakeLink = document.createElement('a'); - - const clickSpy = spyOn(fakeLink, 'click').and.callFake(() => { - // do nothing - }); - - spyOn(document, 'createElement').and.callFake(() => { - return fakeLink; - }); - - const createObjectURLSpy = spyOn(URL, 'createObjectURL').and.callThrough(); - - await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme1', - data: themeData, - }) - .assignTheme({ - animationId: 'lottie1', - themeId: 'theme1', - }) - .build(); - - await dotlottie.download(fileName); - - const blob = await dotlottie.toBlob(); - - expect(createObjectURLSpy).toHaveBeenCalledTimes(1); - expect(createObjectURLSpy).toHaveBeenCalledWith(blob); - - expect(fakeLink.download).toBe(fileName); - expect(fakeLink.style.display).toBe('none'); - expect(clickSpy).toHaveBeenCalledTimes(1); - }); - }); - - describe('toBlob', () => { - it('returns a blob', async () => { - const dotlottie = new DotLottie(); - - const blob = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - const arrayBuffer = await blob.arrayBuffer(); - - expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotlottie = new DotLottie(); - - const blob1 = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBlob({ - zipOptions: { - level: 9, - }, - }); - - const blob2 = await dotlottie.toBlob({ - zipOptions: { - level: 0, - }, - }); - - expect(blob1).toBeInstanceOf(Blob); - expect(blob2).toBeInstanceOf(Blob); - - const arrayBuffer1 = await blob1.arrayBuffer(); - const arrayBuffer2 = await blob2.arrayBuffer(); - - expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); - }); - }); - - describe('toArrayBuffer', () => { - it('returns an array buffer', async () => { - const dotlottie = new DotLottie(); - - const arrayBuffer = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotLottie1 = new DotLottie(); - - const arrayBuffer1 = await dotLottie1 - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toArrayBuffer({ - zipOptions: { - level: 9, - }, - }); - - const arrayBuffer2 = await dotLottie1.toArrayBuffer({ - zipOptions: { - level: 0, - }, - }); - - expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); - expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); - }); - }); - - describe('toBase64', () => { - it('returns base64 string', async () => { - const dotlottie = new DotLottie(); - - const dataURL = await dotlottie - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBase64(); - - const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; - - expect(actualArrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - }); - - it('Controls the compression level of the whole dotLottie file', async () => { - const dotLottie1 = new DotLottie(); - - const dataURL1 = await dotLottie1 - .setAuthor(manifest.author) - .setVersion(manifest.version) - .setGenerator(manifest.generator) - .addAnimation({ - id: manifest.animations[0]?.id as string, - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: manifest.themes[0]?.id as string, - data: themeData, - }) - .assignTheme({ - animationId: manifest.animations[0]?.id as string, - themeId: manifest.themes[0]?.id as string, - }) - .toBase64({ - zipOptions: { - level: 9, - }, - }); - - const dataURL2 = await dotLottie1.toBase64({ - zipOptions: { - level: 0, - }, - }); - - const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; - const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; - - expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); - - expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); - }); - }); - - describe('fromURL', () => { - it('throws an error if the URL is invalid', async () => { - const dotLottie = new DotLottie(); - - await expectAsync(dotLottie.fromURL('invalid-url')).toBeRejectedWithError('[dotlottie-js]: Invalid URL'); - }); - - it('loads a dotlottie file from a URL', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotlottieAnimation)), - ); - - const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await new DotLottie().fromURL(animationURL); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - expect(dotLottie.animations.length).toBe(1); - expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); - expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - expect(dotLottie.manifest).toEqual(manifest as Manifest); - expect(dotLottie.themes.length).toBe(1); - expect(dotLottie.themes[0]?.id).toEqual(manifest.themes[0]?.id); - expect(dotLottie.themes[0]?.data).toEqual(themeData); - }); - - it('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(editedDotlottieAnimation)), - ); - - const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - expect(dotlottie.animations.length).toBe(1); - expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); - expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); - expect(dotlottie.manifest).toEqual(editedManifest as Manifest); - }); - }); - - describe('fromArrayBuffer', () => { - it('loads a dotlottie file from an array buffer', async () => { - const arrayBuffer = dotlottieAnimation; - - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); - - expect(dotlottie.animations.length).toBe(1); - expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); - expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - expect(dotlottie.manifest).toEqual(manifest as Manifest); - expect(dotlottie.themes.length).toBe(1); - expect(dotlottie.themes[0]?.id).toEqual(manifest.themes[0]?.id); - expect(dotlottie.themes[0]?.data).toEqual(themeData); - }); - - it('loads a dotLottie containing images from an array buffer', async () => { - const arrayBuffer = bigMergedDotLottie; - let dotlottie = new DotLottie(); - - dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); - - expect(dotlottie.animations.length).toBe(6); - expect(dotlottie.getImages().length).toBe(16); - expect(dotlottie.animations[0]?.id).toEqual('v1'); - expect(dotlottie.animations[1]?.id).toEqual('v2'); - expect(dotlottie.animations[2]?.id).toEqual('v3'); - expect(dotlottie.animations[3]?.id).toEqual('v4'); - expect(dotlottie.animations[4]?.id).toEqual('v5'); - expect(dotlottie.animations[5]?.id).toEqual('v6'); - expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); - }); - - it('verify image asset loading in a dotLottie animation matches the expected data URL', async () => { - const dotlottie = await new DotLottie().fromArrayBuffer(bigMergedDotLottie); - - const animation1 = await dotlottie.getAnimation(dotlottie.animations[0]?.id ?? ''); - - const unzippedBigMergedDotLottie = unzipSync(new Uint8Array(bigMergedDotLottie)); - - const base64 = Buffer.from( - unzippedBigMergedDotLottie[`images/${animation1?.imageAssets[0]?.fileName}`] as Uint8Array, - ).toString('base64'); - - const expectedDataURL = `data:image/jpeg;base64,${base64}`; - - expect(await animation1?.imageAssets[0]?.toDataURL()).toEqual(expectedDataURL); - }); - }); - - describe('addPlugins', () => { - it('adds plugins to the animation', () => { - class TestPlugin extends DotLottiePlugin { - public readonly name = 'test'; - - public override async onBuild(): Promise { - return Promise.resolve(); - } - } - - const dotlottie = new DotLottie(); - - const plugin = new TestPlugin(); - - dotlottie.addPlugins(plugin); - - expect(dotlottie.plugins).toContain(plugin); - }); - }); - - describe('removePlugins', () => { - it('removes plugins from the animation', () => { - class TestPlugin extends DotLottiePlugin { - public readonly name = 'test'; - - public override async onBuild(): Promise { - return Promise.resolve(); - } - } - - const dotlottie = new DotLottie(); - - const plugin = new TestPlugin(); - - dotlottie.addPlugins(plugin); - - expect(dotlottie.plugins).toContain(plugin); - - dotlottie.removePlugins(plugin); - - expect(dotlottie.plugins).not.toContain(plugin); - }); - }); - - describe('imageAssets', () => { - it('Adds the Bull animation and checks number of images.', async () => { - const dotlottie = await new DotLottie() - .addAnimation({ - id: 'animation_1', - data: structuredClone(bullData) as unknown as AnimationData, - }) - .build(); - - const animation1 = await dotlottie.getAnimation('animation_1'); - - expect(animation1?.imageAssets.length).toBe(5); - }); - }); - - describe('merge', () => { - it('merges two dotlottie files', async () => { - const dotlottie1 = new DotLottie().addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }); - - const dotlottie2 = new DotLottie().addAnimation({ - id: 'lottie2', - data: animationData as unknown as AnimationType, - }); - - const dotlottie3 = new DotLottie().addAnimation({ - id: 'lottie3', - data: structuredClone(bullData as unknown as AnimationData), - }); - - const dotlottie4 = new DotLottie().addAnimation({ - id: 'lottie4', - data: structuredClone(bullData as unknown as AnimationData), - }); - - const shrekVariant1 = new DotLottie().addAnimation({ - id: 'v1', - data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), - }); - - const shrekVariant2 = new DotLottie().addAnimation({ - id: 'v2', - data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), - }); - - const shrekVariant3 = new DotLottie().addAnimation({ - id: 'v3', - data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), - }); - - const shrekVariant4 = new DotLottie().addAnimation({ - id: 'v4', - data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), - }); - - const shrekVariant5 = new DotLottie().addAnimation({ - id: 'v5', - data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), - }); - - const shrekVariant6 = new DotLottie().addAnimation({ - id: 'v6', - data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), - }); - - const [mergedImageLottie] = await Promise.all([ - new DotLottie().merge(dotlottie3, dotlottie4), - new DotLottie().merge(dotlottie3, dotlottie4).build(), - ]); - - const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ - new DotLottie() - .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) - .build(), - new DotLottie().merge(dotlottie1, dotlottie2).build(), - ]); - - expect(mergedImageLottie.animations.length).toBe(2); - - expect(bigMergedImageLottie.animations.length).toBe(6); - - expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ - 'v1', - 'v2', - 'v3', - 'v4', - 'v5', - 'v6', - ]); - - expect(mergedDotlottie).toBeInstanceOf(DotLottie); - - expect(mergedDotlottie.animations.length).toBe(2); - - expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); - expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - - expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); - expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); - }); - }); - - describe('build', () => { - it('it resolves lottie animations', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation.json'; - - const dotlottie = new DotLottie().addAnimation({ - url: animationURL, - id: 'lottie1', - }); - - expect(dotlottie.animations[0]?.data).toBeUndefined(); - - await dotlottie.build(); - - expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - }); - - it('runs plugins in correct order', async () => { - const parallel1OnBuild = jasmine.createSpy('parallel1OnBuild'); - const parallel2OnBuild = jasmine.createSpy('parallel2OnBuild'); - - const sequential1OnBuild = jasmine.createSpy('sequential1OnBuild'); - const sequential2OnBuild = jasmine.createSpy('sequential2OnBuild'); - - class Parallel1TestPlugin extends DotLottiePlugin { - public readonly name = 'parallel-test'; - - public constructor() { - super({ parallel: true }); - } - - public override async onBuild(): Promise { - parallel1OnBuild(this.dotlottie); - } - } - class Parallel2TestPlugin extends DotLottiePlugin { - public readonly name = 'parallel-test'; - - public constructor() { - super({ parallel: true }); - } - - public override async onBuild(): Promise { - parallel2OnBuild(this.dotlottie); - } - } - - class Sequential1TestPlugin extends DotLottiePlugin { - public readonly name = 'sequential-test'; - - public override async onBuild(): Promise { - sequential1OnBuild(this.dotlottie); - } - } - - class Sequential2TestPlugin extends DotLottiePlugin { - public readonly name = 'sequential-test'; - - public override async onBuild(): Promise { - sequential2OnBuild(this.dotlottie); - } - } - - const dotlottie = new DotLottie() - .addAnimation({ - id: 'lottie1', - data: animationData as unknown as AnimationType, - }) - .addPlugins( - new Sequential1TestPlugin(), - new Parallel1TestPlugin(), - new Parallel2TestPlugin(), - new Sequential2TestPlugin(), - ); - - expect(parallel1OnBuild).not.toHaveBeenCalled(); - expect(parallel2OnBuild).not.toHaveBeenCalled(); - expect(sequential1OnBuild).not.toHaveBeenCalled(); - expect(sequential2OnBuild).not.toHaveBeenCalled(); - - await dotlottie.build(); - - expect(parallel1OnBuild).toHaveBeenCalledTimes(1); - expect(parallel2OnBuild).toHaveBeenCalledTimes(1); - expect(sequential1OnBuild).toHaveBeenCalledTimes(1); - expect(sequential2OnBuild).toHaveBeenCalledTimes(1); - - expect(parallel1OnBuild).toHaveBeenCalledWith(dotlottie); - expect(parallel2OnBuild).toHaveBeenCalledWith(dotlottie); - expect(sequential1OnBuild).toHaveBeenCalledWith(dotlottie); - expect(sequential2OnBuild).toHaveBeenCalledWith(dotlottie); - - expect(parallel1OnBuild).toHaveBeenCalledBefore(parallel2OnBuild); - expect(parallel2OnBuild).toHaveBeenCalledBefore(sequential1OnBuild); - expect(sequential1OnBuild).toHaveBeenCalledBefore(sequential2OnBuild); - }); - - describe('addTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - // act - const result = dotlottie.addTheme(theme); - - // assert - expect(result).toBe(dotlottie); - }); - - it('adds theme', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme1 = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const theme2 = { - id: 'theme_2', - url: 'https://fake.lottiefiles.com/theme.json', - }; - - // act - dotlottie.addTheme(theme1).addTheme(theme2); - - const themes = dotlottie.themes; - - // assert - expect(themes.length).toBe(2); - - expect(themes[0]).toBeInstanceOf(LottieThemeCommon); - expect(themes[0]?.id).toBe(theme1.id); - expect(themes[0]?.data).toBe(theme1.data); - expect(themes[0]?.url).toBeUndefined(); - - expect(themes[1]).toBeInstanceOf(LottieThemeCommon); - expect(themes[1]?.id).toBe(theme2.id); - expect(themes[1]?.url).toBe(theme2.url); - expect(themes[1]?.data).toBeUndefined(); - }); - }); - - describe('removeTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - // act - const result = dotlottie.addTheme(theme).removeTheme(theme.id); - - // assert - expect(result).toBe(dotlottie); - }); - - it('removes theme', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme1 = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const theme2 = { - id: 'theme_2', - url: 'https://fake.lottiefiles.com/theme.json', - }; - - // act - dotlottie.addTheme(theme1).addTheme(theme2).removeTheme(theme1.id); - - // assert - expect(dotlottie.themes.length).toBe(1); - expect(dotlottie.themes[0]?.id).toBe(theme2.id); - }); - }); - - describe('getTheme', () => { - it('returns theme by id', () => { - // arrange - const dotLottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - dotLottie.addTheme(theme); - - // act - const result = dotLottie.getTheme(theme.id); - - // assert - expect(result).toBeInstanceOf(LottieThemeCommon); - expect(result?.id).toBe(theme.id); - expect(result?.data).toBe(theme.data); - }); - - it('returns undefined if theme does not exist', () => { - // arrange - const dotLottie = new DotLottie(); - - // act - const result = dotLottie.getTheme('theme_1'); - - // assert - expect(result).toBeUndefined(); - }); - }); - - describe('assignTheme', () => { - it('returns dotLottie instance', () => { - // arrange - const dotlottie = new DotLottie(); - - // act - const result = dotlottie - .addAnimation({ - id: 'animation_1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }) - .assignTheme({ - themeId: 'theme_1', - animationId: 'animation_1', - }); - - // assert - expect(result).toBe(dotlottie); - }); - - it('throws error if animation does not exist', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const animation = { - id: 'animation_1', - data: animationData as unknown as AnimationType, - }; - - expect(() => { - // act - dotlottie.addTheme(theme).assignTheme({ - themeId: theme.id, - animationId: animation.id, - }); - // assert - }).toThrowError(`[dotlottie-js]: Failed to find animation with id ${animation.id}`); - }); - - it('throws error if theme does not exist', () => { - // arrange - const dotlottie = new DotLottie(); - - const theme = { - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }; - - const animation = { - id: 'animation_1', - data: animationData as unknown as AnimationType, - }; - - expect(() => { - // act - dotlottie.addAnimation(animation).assignTheme({ - themeId: theme.id, - animationId: animation.id, - }); - // assert - }).toThrowError(`[dotlottie-js]: Failed to find theme with id ${theme.id}`); - }); - - it('assigns an existing theme to an existing animation', async () => { - // arrange - const dotlottie = new DotLottie(); - - // act - dotlottie - .addAnimation({ - id: 'animation_1', - data: animationData as unknown as AnimationType, - }) - .addTheme({ - id: 'theme_1', - data: { - ['face_color']: { - p: { - a: 0, - k: [1, 0, 0, 1], - ix: 4, - }, - }, - }, - }) - .assignTheme({ - themeId: 'theme_1', - animationId: 'animation_1', - }); - - // assert - const assignedTheme = dotlottie.getTheme('theme_1'); - const animation = await dotlottie.getAnimation('animation_1'); - - expect(assignedTheme?.animations.length).toBe(1); - expect(assignedTheme?.animations[0]).toBe(animation); - - expect(animation?.themes.length).toBe(1); - expect(animation?.themes[0]).toBe(assignedTheme); - - expect(dotlottie.manifest.themes).toEqual([ - { - id: 'theme_1', - animations: ['animation_1'], - }, - ]); - }); - }); - }); -}); diff --git a/packages/dotlottie-js/src/tests/lottie-animation-browser.spec.ts b/packages/dotlottie-js/src/tests/lottie-animation-browser.spec.ts deleted file mode 100644 index 0c53e981..00000000 --- a/packages/dotlottie-js/src/tests/lottie-animation-browser.spec.ts +++ /dev/null @@ -1,310 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable no-new */ -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import { Base64 } from 'js-base64'; - -import { LottieAnimation, LottieTheme } from '..'; - -import BULL_DATA from './__fixtures__/image-asset-optimization/bull.json'; -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; - -describe('LottieAnimation', () => { - it('throws an error if it receives an invalid id when constructed', () => { - expect(() => { - new LottieAnimation({ id: '' }); - }).toThrowError('[dotlottie-js]: Invalid animation id'); - }); - - it('throws an error if it receives an invalid url when constructed', () => { - const invalidUrl = 'xyz'; - - expect(() => { - new LottieAnimation({ id: 'test', url: invalidUrl }); - }).toThrowError('[dotlottie-js]: Invalid animation url'); - }); - - it('throws an error if it receives an invalid lottie data when constructed', () => { - const invalidData = {} as AnimationType; - - expect(() => { - new LottieAnimation({ id: 'test', data: invalidData }); - }).toThrowError('[dotlottie-js]: Received invalid Lottie data.'); - }); - - it('throws an error if it receives an no data or url when constructed', () => { - expect(() => { - new LottieAnimation({ id: 'test' }); - }).toThrowError('[dotlottie-js]: No data or url provided.'); - }); - - it('gets and sets the zipOptions', () => { - const animation = new LottieAnimation({ - id: 'test', - data: animationData as unknown as AnimationType, - zipOptions: { - level: 9, - mem: 1, - }, - }); - - expect(animation.zipOptions).toEqual({ - level: 9, - mem: 1, - }); - - animation.zipOptions = { - level: 1, - }; - - expect(animation.zipOptions).toEqual({ - level: 1, - }); - }); - - it('gets and sets the id', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.id).toEqual('test'); - - animation.id = 'test2'; - - expect(animation.id).toEqual('test2'); - }); - - it('gets and sets the data', () => { - const animation = new LottieAnimation({ id: 'test', url: 'https://example.com' }); - - expect(animation.data).toBeUndefined(); - - animation.data = animationData as unknown as AnimationType; - - expect(animation.data).toEqual(animationData as unknown as AnimationType); - }); - - it('gets and sets the url', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.url).toBeUndefined(); - - animation.url = 'https://example.com'; - - expect(animation.url).toEqual('https://example.com'); - }); - - it('gets and sets the default theme', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.defaultTheme).toBeUndefined(); - - animation.defaultTheme = 'theme1'; - - expect(animation.defaultTheme).toEqual('theme1'); - }); - - it('gets assigned themes', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.themes).toEqual([]); - - const theme = new LottieTheme({ id: 'theme1', url: 'http://fake.lottiefiles.com/theme.json' }); - - animation.addTheme(theme); - - expect(animation.themes).toEqual([theme]); - }); - - describe('toJSON', () => { - it('returns the animation data as a JSON object', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const jsonData = await animation.toJSON(); - - expect(jsonData).toEqual(animationData as unknown as AnimationType); - }); - - it('returns the animation with inlined data as a JSON object', async () => { - const animation = new LottieAnimation({ - id: 'test', - data: structuredClone(BULL_DATA) as unknown as unknown as AnimationType, - }); - - const jsonData = await animation.toJSON({ inlineAssets: true }); - - expect(jsonData).toEqual(BULL_DATA as unknown as unknown as AnimationType); - }); - - it('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const jsonData = await animation.toJSON(); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - expect(jsonData).toEqual(animationData as unknown as AnimationType); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toJSON()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toBase64', () => { - it('returns the base64 of the animation', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const dataUrl = await animation.toBase64(); - - expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); - }); - - it('resolves the animation data from the provided url and returns the animation data as a data url', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const dataUrl = await animation.toBase64(); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toBase64()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toBlob', () => { - it('returns the animation data as a blob', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const blob = await animation.toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - const blobText = await blob.text(); - - expect(blobText).toEqual(JSON.stringify(animationData)); - }); - - it('resolves the animation data from the provided url and returns the animation data as a blob', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const blob = await animation.toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - const blobText = await blob.text(); - - expect(blobText).toEqual(JSON.stringify(animationData)); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toBlob()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toArrayBuffer', () => { - it('returns the animation data as an array buffer', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const arrayBuffer = await animation.toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - - const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); - - expect(arrayBufferText).toEqual(JSON.stringify(animationData)); - }); - - it('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const arrayBuffer = await animation.toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); - - expect(arrayBufferText).toEqual(JSON.stringify(animationData)); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toArrayBuffer()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); -}); diff --git a/packages/dotlottie-js/src/tests/lottie-animation-node.spec.ts b/packages/dotlottie-js/src/tests/lottie-animation-node.spec.ts deleted file mode 100644 index b0bc167e..00000000 --- a/packages/dotlottie-js/src/tests/lottie-animation-node.spec.ts +++ /dev/null @@ -1,311 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable no-new */ - -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import { Base64 } from 'js-base64'; - -import { LottieAnimation, LottieTheme } from '../node'; - -import BULL_DATA from './__fixtures__/image-asset-optimization/bull.json'; -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; - -describe('LottieAnimation', () => { - it('throws an error if it receives an invalid id when constructed', () => { - expect(() => { - new LottieAnimation({ id: '' }); - }).toThrowError('[dotlottie-js]: Invalid animation id'); - }); - - it('throws an error if it receives an invalid url when constructed', () => { - const invalidUrl = 'xyz'; - - expect(() => { - new LottieAnimation({ id: 'test', url: invalidUrl }); - }).toThrowError('[dotlottie-js]: Invalid animation url'); - }); - - it('throws an error if it receives an invalid lottie data when constructed', () => { - const invalidData = {} as AnimationType; - - expect(() => { - new LottieAnimation({ id: 'test', data: invalidData }); - }).toThrowError('[dotlottie-js]: Received invalid Lottie data.'); - }); - - it('throws an error if it receives an no data or url when constructed', () => { - expect(() => { - new LottieAnimation({ id: 'test' }); - }).toThrowError('[dotlottie-js]: No data or url provided.'); - }); - - it('gets and sets the zipOptions', () => { - const animation = new LottieAnimation({ - id: 'test', - data: animationData as unknown as AnimationType, - zipOptions: { - level: 9, - mem: 1, - }, - }); - - expect(animation.zipOptions).toEqual({ - level: 9, - mem: 1, - }); - - animation.zipOptions = { - level: 1, - }; - - expect(animation.zipOptions).toEqual({ - level: 1, - }); - }); - - it('gets and sets the id', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.id).toEqual('test'); - - animation.id = 'test2'; - - expect(animation.id).toEqual('test2'); - }); - - it('gets and sets the data', () => { - const animation = new LottieAnimation({ id: 'test', url: 'https://example.com' }); - - expect(animation.data).toBeUndefined(); - - animation.data = animationData as unknown as AnimationType; - - expect(animation.data).toEqual(animationData as unknown as AnimationType); - }); - - it('gets and sets the url', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.url).toBeUndefined(); - - animation.url = 'https://example.com'; - - expect(animation.url).toEqual('https://example.com'); - }); - - it('gets and sets the default theme', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.defaultTheme).toBeUndefined(); - - animation.defaultTheme = 'theme1'; - - expect(animation.defaultTheme).toEqual('theme1'); - }); - - it('gets assigned themes', () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - expect(animation.themes).toEqual([]); - - const theme = new LottieTheme({ id: 'theme1', url: 'http://fake.lottiefiles.com/theme.lss' }); - - animation.addTheme(theme); - - expect(animation.themes).toEqual([theme]); - }); - - describe('toJSON', () => { - it('returns the animation data as a JSON object', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const jsonData = await animation.toJSON(); - - expect(jsonData).toEqual(animationData as unknown as AnimationType); - }); - - it('returns the animation with inlined data as a JSON object', async () => { - const animation = new LottieAnimation({ - id: 'test', - data: structuredClone(BULL_DATA) as unknown as AnimationType, - }); - - const jsonData = await animation.toJSON({ inlineAssets: true }); - - expect(jsonData).toEqual(BULL_DATA as unknown as AnimationType); - }); - - it('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const jsonData = await animation.toJSON(); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - expect(jsonData).toEqual(animationData as unknown as AnimationType); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toJSON()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toBase64', () => { - it('returns the base64 of the animation', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const dataUrl = await animation.toBase64(); - - expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); - }); - - it('resolves the animation data from the provided url and returns the animation data as a data url', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const dataUrl = await animation.toBase64(); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toBase64()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toBlob', () => { - it('returns the animation data as a blob', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const blob = await animation.toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - const blobText = await blob.text(); - - expect(blobText).toEqual(JSON.stringify(animationData)); - }); - - it('resolves the animation data from the provided url and returns the animation data as a blob', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const blob = await animation.toBlob(); - - expect(blob).toBeInstanceOf(Blob); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - const blobText = await blob.text(); - - expect(blobText).toEqual(JSON.stringify(animationData)); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toBlob()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); - - describe('toArrayBuffer', () => { - it('returns the animation data as an array buffer', async () => { - const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); - - const arrayBuffer = await animation.toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - - const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); - - expect(arrayBufferText).toEqual(JSON.stringify(animationData)); - }); - - it('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(animationData))), - ); - - const animationURL = 'https://lottiefiles.fake/animation/test.json'; - - const animation = new LottieAnimation({ - id: 'test', - url: animationURL, - }); - - const arrayBuffer = await animation.toArrayBuffer(); - - expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); - - expect(fetchSpy).toHaveBeenCalledWith(animationURL); - - const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); - - expect(arrayBufferText).toEqual(JSON.stringify(animationData)); - }); - - it('throws an error if the animation data cannot be resolved from the provided url', async () => { - const animation = new LottieAnimation({ - id: 'test', - url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', - }); - - await expectAsync(animation.toArrayBuffer()).toBeRejectedWithError( - // eslint-disable-next-line optimize-regex/optimize-regex - /\[dotlottie-js\]:\s.+:?\sInvalid json returned from url/u, - ); - }); - }); -}); diff --git a/packages/dotlottie-js/src/tests/lottie-theme-browser.spec.ts b/packages/dotlottie-js/src/tests/lottie-theme-browser.spec.ts deleted file mode 100644 index bcd22857..00000000 --- a/packages/dotlottie-js/src/tests/lottie-theme-browser.spec.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable no-new */ - -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; - -import { LottieTheme, LottieAnimation } from '..'; - -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; -import themeData from './__fixtures__/simple/animation/themes/theme1.json'; - -describe('LottieTheme', () => { - it('throws an error if it receives an invalid id when constructed', () => { - expect(() => { - // act - new LottieTheme({ id: '' }); - // assert - }).toThrowError('[dotlottie-js]: Invalid theme id'); - }); - - it('throws an error if it receives an invalid url when constructed', () => { - // arrange - const invalidUrl = 'xyz'; - - expect(() => { - // act - new LottieTheme({ id: 'test', url: invalidUrl }); - - // assert - }).toThrowError('[dotlottie-js]: Invalid theme url'); - }); - - it('throws an error if it receives an invalid theme data when constructed', () => { - // arrange - const invalidData = 'invalid' as unknown as Record; - - expect(() => { - // act - new LottieTheme({ id: 'test', data: invalidData }); - - // assert - }).toThrowError('[dotlottie-js]: Invalid theme data'); - }); - - it('gets and sets the zipOptions', () => { - const theme = new LottieTheme({ - id: 'test', - data: themeData, - zipOptions: { - level: 9, - mem: 1, - }, - }); - - expect(theme.zipOptions).toEqual({ - level: 9, - mem: 1, - }); - - theme.zipOptions = { - level: 1, - }; - - expect(theme.zipOptions).toEqual({ - level: 1, - }); - }); - - it('gets and sets the id', () => { - // arrange - const animation = new LottieTheme({ id: 'test', data: themeData }); - - expect(animation.id).toEqual('test'); - - // act - animation.id = 'test2'; - - // assert - expect(animation.id).toEqual('test2'); - }); - - it('gets and sets the data', () => { - // arrange - const animation = new LottieTheme({ id: 'test', url: 'https://example.com' }); - - expect(animation.data).toBeUndefined(); - - // act - animation.data = themeData; - - // assert - expect(animation.data).toEqual(themeData); - }); - - it('gets and sets the url', () => { - // arrange - const animation = new LottieTheme({ id: 'test', data: themeData }); - - expect(animation.url).toBeUndefined(); - - // act - animation.url = 'https://example.com'; - - // assert - expect(animation.url).toEqual('https://example.com'); - }); - - it('adds an animation', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - expect(theme.animations.length).toEqual(0); - - // act - theme.addAnimation(animation); - - // assert - expect(theme.animations.length).toEqual(1); - - expect(theme.animations[0]).toEqual(animation); - }); - - it('removes an animation', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - expect(theme.animations.length).toEqual(0); - - theme.addAnimation(animation); - - expect(theme.animations.length).toEqual(1); - - expect(theme.animations[0]).toEqual(animation); - - // act - theme.removeAnimation(animation.id); - - // assert - expect(theme.animations.length).toEqual(0); - }); - - it('gets animations', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation1 = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - const animation2 = new LottieAnimation({ - id: 'animation2', - data: animationData as unknown as AnimationType, - }); - - // act - theme.addAnimation(animation1); - theme.addAnimation(animation2); - - // assert - expect(theme.animations.length).toEqual(2); - - expect(theme.animations[0]).toEqual(animation1); - expect(theme.animations[1]).toEqual(animation2); - }); - - it('resolves theme data from a url', async () => { - // arrange - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(themeData))), - ); - - const theme = new LottieTheme({ id: 'theme1', url: 'https://example.com' }); - - expect(theme.data).toBeUndefined(); - - // act - const content = await theme.toString(); - - // assert - expect(content).toEqual(JSON.stringify(themeData)); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith('https://example.com'); - - expect(theme.data).toEqual(themeData); - }); -}); diff --git a/packages/dotlottie-js/src/tests/lottie-theme-node.spec.ts b/packages/dotlottie-js/src/tests/lottie-theme-node.spec.ts deleted file mode 100644 index bcd22857..00000000 --- a/packages/dotlottie-js/src/tests/lottie-theme-node.spec.ts +++ /dev/null @@ -1,201 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ -/* eslint-disable no-new */ - -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; - -import { LottieTheme, LottieAnimation } from '..'; - -import animationData from './__fixtures__/simple/animation/animations/lottie1.json'; -import themeData from './__fixtures__/simple/animation/themes/theme1.json'; - -describe('LottieTheme', () => { - it('throws an error if it receives an invalid id when constructed', () => { - expect(() => { - // act - new LottieTheme({ id: '' }); - // assert - }).toThrowError('[dotlottie-js]: Invalid theme id'); - }); - - it('throws an error if it receives an invalid url when constructed', () => { - // arrange - const invalidUrl = 'xyz'; - - expect(() => { - // act - new LottieTheme({ id: 'test', url: invalidUrl }); - - // assert - }).toThrowError('[dotlottie-js]: Invalid theme url'); - }); - - it('throws an error if it receives an invalid theme data when constructed', () => { - // arrange - const invalidData = 'invalid' as unknown as Record; - - expect(() => { - // act - new LottieTheme({ id: 'test', data: invalidData }); - - // assert - }).toThrowError('[dotlottie-js]: Invalid theme data'); - }); - - it('gets and sets the zipOptions', () => { - const theme = new LottieTheme({ - id: 'test', - data: themeData, - zipOptions: { - level: 9, - mem: 1, - }, - }); - - expect(theme.zipOptions).toEqual({ - level: 9, - mem: 1, - }); - - theme.zipOptions = { - level: 1, - }; - - expect(theme.zipOptions).toEqual({ - level: 1, - }); - }); - - it('gets and sets the id', () => { - // arrange - const animation = new LottieTheme({ id: 'test', data: themeData }); - - expect(animation.id).toEqual('test'); - - // act - animation.id = 'test2'; - - // assert - expect(animation.id).toEqual('test2'); - }); - - it('gets and sets the data', () => { - // arrange - const animation = new LottieTheme({ id: 'test', url: 'https://example.com' }); - - expect(animation.data).toBeUndefined(); - - // act - animation.data = themeData; - - // assert - expect(animation.data).toEqual(themeData); - }); - - it('gets and sets the url', () => { - // arrange - const animation = new LottieTheme({ id: 'test', data: themeData }); - - expect(animation.url).toBeUndefined(); - - // act - animation.url = 'https://example.com'; - - // assert - expect(animation.url).toEqual('https://example.com'); - }); - - it('adds an animation', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - expect(theme.animations.length).toEqual(0); - - // act - theme.addAnimation(animation); - - // assert - expect(theme.animations.length).toEqual(1); - - expect(theme.animations[0]).toEqual(animation); - }); - - it('removes an animation', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - expect(theme.animations.length).toEqual(0); - - theme.addAnimation(animation); - - expect(theme.animations.length).toEqual(1); - - expect(theme.animations[0]).toEqual(animation); - - // act - theme.removeAnimation(animation.id); - - // assert - expect(theme.animations.length).toEqual(0); - }); - - it('gets animations', () => { - // arrange - const theme = new LottieTheme({ id: 'theme1', data: themeData }); - - const animation1 = new LottieAnimation({ - id: 'animation1', - data: animationData as unknown as AnimationType, - }); - - const animation2 = new LottieAnimation({ - id: 'animation2', - data: animationData as unknown as AnimationType, - }); - - // act - theme.addAnimation(animation1); - theme.addAnimation(animation2); - - // assert - expect(theme.animations.length).toEqual(2); - - expect(theme.animations[0]).toEqual(animation1); - expect(theme.animations[1]).toEqual(animation2); - }); - - it('resolves theme data from a url', async () => { - // arrange - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(JSON.stringify(themeData))), - ); - - const theme = new LottieTheme({ id: 'theme1', url: 'https://example.com' }); - - expect(theme.data).toBeUndefined(); - - // act - const content = await theme.toString(); - - // assert - expect(content).toEqual(JSON.stringify(themeData)); - - expect(fetchSpy).toHaveBeenCalledTimes(1); - expect(fetchSpy).toHaveBeenCalledWith('https://example.com'); - - expect(theme.data).toEqual(themeData); - }); -}); diff --git a/packages/dotlottie-js/src/tests/test-utils.ts b/packages/dotlottie-js/src/tests/test-utils.ts deleted file mode 100644 index 531189a7..00000000 --- a/packages/dotlottie-js/src/tests/test-utils.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -import { unzipSync, strFromU8 } from 'fflate'; - -const toBeEqualDotlottieArrayBuffer: jasmine.CustomMatcherFactory = (matchersUtil: jasmine.MatchersUtil) => { - return { - compare: (actual: ArrayBuffer, expected: ArrayBuffer): jasmine.CustomMatcherResult => { - const result: jasmine.CustomMatcherResult = { - pass: false, - }; - - const actualUint8Array = new Uint8Array(actual); - const expectedUint8Array = new Uint8Array(expected); - - const actualUnzipped = unzipSync(actualUint8Array); - const expectedUnzipped = unzipSync(expectedUint8Array); - - // eslint-disable-next-line guard-for-in - for (const key in actualUnzipped) { - if (!(key in expectedUnzipped)) { - result.message = `actual dotlottie has ${key} that is not in the expected dotlottie`; - - return result; - } - - const actualValue = actualUnzipped[key] as Uint8Array; - const expectedValue = expectedUnzipped[key] as Uint8Array; - - if (key === 'manifest.json') { - const actualManifest = JSON.parse(strFromU8(actualValue)); - const expectedManifest = JSON.parse(strFromU8(expectedValue)); - - const areEqual = matchersUtil.equals(actualManifest, expectedManifest); - - if (!areEqual) { - result.message = `expected dotlottie manifest to equal ${JSON.stringify( - expectedManifest, - null, - 2, - )} but got ${JSON.stringify(actualManifest, null, 2)}}`; - - return result; - } - } - - if (key.startsWith('animations/')) { - const actualAnimation = JSON.parse(strFromU8(actualValue)); - const expectedAnimation = JSON.parse(strFromU8(expectedValue)); - - const areEqual = matchersUtil.equals(actualAnimation, expectedAnimation); - - if (!areEqual) { - result.message = `expected dotlottie animation ${key} to equal the actual dotlottie animation ${key}`; - - return result; - } - } - } - - result.pass = true; - - return result; - }, - }; -}; - -export const customMatchers = { - toBeEqualDotlottieArrayBuffer, -}; diff --git a/packages/dotlottie-js/src/tests/utils-browser.spec.ts b/packages/dotlottie-js/src/tests/utils-browser.spec.ts deleted file mode 100644 index c435583d..00000000 --- a/packages/dotlottie-js/src/tests/utils-browser.spec.ts +++ /dev/null @@ -1,363 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ - -import { zipSync, unzipSync } from 'fflate'; - -import type { AnimationData, Manifest } from '..'; -import { - DotLottieError, - ErrorCodes, - dataUrlFromU8, - getAnimation, - getAnimations, - getImage, - getImages, - getManifest, - getTheme, - getThemes, - loadFromURL, - createError, - isValidURL, - getStateMachine, - getStateMachines, - getAudio, - getAllAudio, -} from '..'; - -import dotLottieAnimationWithAudio from './__fixtures__/audio/2_instrument_animations.lottie'; -import dotLottieAnimation from './__fixtures__/simple/animation.lottie'; -import bullJson from './__fixtures__/simple/animation/animations/bull.json'; -import dotLottieLottie1 from './__fixtures__/simple/animation/animations/lottie1.json'; -import dotLottieManifest from './__fixtures__/simple/animation/manifest.json'; -import dotLottieTheme from './__fixtures__/simple/animation/themes/theme1.json'; -import dotLottieAnimationWithImages from './__fixtures__/simple/big-merged-dotlottie.lottie'; -import bullAnimation from './__fixtures__/simple/bull.lottie'; -import stateAnimation from './__fixtures__/simple/exploding_pigeon.lottie'; -import { PigeonState, PigeonWithoutExplosion } from './__fixtures__/simple/state/pigeon-state'; - -describe('createError', () => { - it('returns an instance of Error with the correct message', () => { - const errorMessage = 'This is an error'; - const error = createError(errorMessage); - - expect(error).toBeInstanceOf(Error); - expect(error.message).toBe(`[dotlottie-js]: ${errorMessage}`); - }); -}); - -describe('isValidURL', () => { - it('returns true for a valid URL', () => { - expect(isValidURL('https://www.valid.com')).toBe(true); - }); - - it('returns false for an invalid URL', () => { - expect(isValidURL('invalid')).toBe(false); - }); -}); - -describe('loadFromUrl', () => { - it('throws when url is not valid', async () => { - await expectAsync(loadFromURL('')).toBeRejectedWith( - new DotLottieError('Invalid url provided for .lottie file', ErrorCodes.INVALID_DOTLOTTIE), - ); - }); - - it('throws an error if invalid content-type is returned', async () => { - spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'text/html' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWith( - new DotLottieError( - 'Invalid content type for .lottie file, expected application/zip or application/octet-stream, received text/html', - ErrorCodes.INVALID_DOTLOTTIE, - ), - ); - }); - - it('loads a dotlottie from a url with content-type application/octet-stream', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'application/octet-stream' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await loadFromURL(dotLottieURL); - - expect(dotLottie).toBeDefined(); - expect(dotLottie).toBeInstanceOf(Uint8Array); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('loads a dotlottie from a url', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await loadFromURL(dotLottieURL); - - expect(dotLottie).toBeDefined(); - expect(dotLottie).toBeInstanceOf(Uint8Array); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('throws error if dotlottie with no manifest is loaded', async () => { - const data: Record = {}; - - // convert the lottie to uint8array - data['animations/lottie1.json'] = new TextEncoder().encode(JSON.stringify(dotLottieLottie1)); - - const dotLottieWithNoManifest = zipSync(data); - - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieWithNoManifest, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWith( - new DotLottieError('Invalid .lottie file, manifest.json is missing', ErrorCodes.INVALID_DOTLOTTIE), - ); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('throws error if manifest.json has invalid structure', async () => { - const data: Record = {}; - - data['manifest.json'] = new TextEncoder().encode( - JSON.stringify({ - version: '1.0', - revision: 1, - keywords: 'dotLottie', - author: 'LottieFiles', - generator: 'dotLottie-js_v2.0', - // animations array is missing - themes: [{ id: 'theme1', animations: ['lottie1'] }], - }), - ); - - const dotLottieWithInvalidManifest = zipSync(data); - - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieWithInvalidManifest, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWithError( - /invalid .lottie file, manifest.json structure is invalid/iu, - ); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); -}); - -describe('getManifest', () => { - it('return manifest from dotlottie', async () => { - const manifest = await getManifest(dotLottieAnimation); - - expect(manifest).toEqual(dotLottieManifest as Manifest); - }); - - it('returns undefined if manifest is not found', async () => { - const data: Record = {}; - - // convert the lottie to uint8array - data['animations/lottie1.json'] = new TextEncoder().encode(JSON.stringify(dotLottieLottie1)); - - const dotLottieWithNoManifest = zipSync(data); - - const manifest = await getManifest(dotLottieWithNoManifest); - - expect(manifest).toBeUndefined(); - }); -}); - -describe('getImage', () => { - it('returns undefined if image is not found', async () => { - const image = await getImage(dotLottieAnimation, 'invalid_image'); - - expect(image).toBeUndefined(); - }); -}); - -describe('getAudio', () => { - it('returns the audio', async () => { - const audio = await getAudio(dotLottieAnimationWithAudio, 'audio_1.mpeg'); - - expect(audio?.length).toBeGreaterThan(0); - }); - - it('returns undefined if audio is not found', async () => { - const audio = await getAudio(dotLottieAnimationWithAudio, 'invalid_audio'); - - expect(audio).toBeUndefined(); - }); -}); - -describe('getAnimation', () => { - it('returns undefined if animation not found', async () => { - const animation = await getAnimation(dotLottieAnimation, 'invalid_animation'); - - expect(animation).toBeUndefined(); - }); - - it('get animation by id', async () => { - const animation = await getAnimation(dotLottieAnimation, 'lottie1'); - - expect(animation).toEqual(dotLottieLottie1 as AnimationData); - }); - - it('returns inlined images within the animation', async () => { - const manifest = await getManifest(bullAnimation); - - const animationId = manifest?.animations[0]?.id || ''; - - const animation = await getAnimation(bullAnimation, animationId, { inlineAssets: true }); - - expect(JSON.stringify(animation?.assets)).toEqual(JSON.stringify(bullJson.assets)); - }); -}); - -describe('getTheme', () => { - it('returns undefined if theme not found', async () => { - const theme = await getTheme(dotLottieAnimation, 'invalid_theme'); - - expect(theme).toBeUndefined(); - }); - - it('gets theme by id', async () => { - const theme = await getTheme(dotLottieAnimation, 'theme1'); - - expect(theme).toEqual(dotLottieTheme); - }); -}); - -describe('getStateMachine', () => { - it('returns undefined if state machine is not found', async () => { - const stateMachine = await getStateMachine(stateAnimation, 'invalid_state'); - - expect(stateMachine).toBeUndefined(); - }); - - it('gets state machine by id', async () => { - const stateMachine = await getStateMachine(stateAnimation, 'explodingPigeon'); - - expect(stateMachine?.states).toEqual(PigeonState.states); - }); -}); - -describe('getImages', () => { - it('returns a map of images', async () => { - const images = await getImages(dotLottieAnimationWithImages); - - const unzippedDotLottie = unzipSync(dotLottieAnimationWithImages); - const expectedImages: Record = {}; - - // eslint-disable-next-line guard-for-in - for (const key in unzippedDotLottie) { - const data = unzippedDotLottie[key]; - - if (key.startsWith('images/') && data) { - expectedImages[key.replace('images/', '')] = await dataUrlFromU8(data); - } - } - - expect(images).toEqual(expectedImages); - }); - - it('returns a map of images with filter', async () => { - const images = await getImages(dotLottieAnimationWithImages, (file) => file.name.startsWith('images/invalid')); - - expect(images).toEqual({}); - }); -}); - -describe('getAllAudio', () => { - it('returns a map of all the audio files', async () => { - const audio = await getAllAudio(dotLottieAnimationWithAudio); - - const unzippedDotLottie = unzipSync(dotLottieAnimationWithAudio); - const expectedAudio: Record = {}; - - // eslint-disable-next-line guard-for-in - for (const key in unzippedDotLottie) { - const data = unzippedDotLottie[key]; - - if (key.startsWith('audio/') && data) { - expectedAudio[key.replace('audio/', '')] = await dataUrlFromU8(data); - } - } - - expect(audio).toEqual(expectedAudio); - }); - - it('returns a map of audio with filter', async () => { - const audio = await getAllAudio(dotLottieAnimationWithImages, (file) => file.name.startsWith('audio/invalid')); - - expect(audio).toEqual({}); - }); -}); - -describe('getThemes', () => { - it('returns a map of themes', async () => { - const themes = await getThemes(dotLottieAnimation); - - const expectedThemes = { - theme1: dotLottieTheme, - }; - - expect(themes).toEqual(expectedThemes); - }); - - it('returns a map of themes with filter', async () => { - const themes = await getThemes(dotLottieAnimation, (file) => file.name.startsWith('themes/invalid')); - - expect(themes).toEqual({}); - }); -}); - -describe('getStateMachines', () => { - it('returns a map of state machines', async () => { - const stateMachines = await getStateMachines(stateAnimation); - - expect(JSON.parse(stateMachines['pigeonWithoutExplosion'] ?? '')).toEqual(PigeonWithoutExplosion); - expect(JSON.parse(stateMachines['explodingPigeon'] ?? '')).toEqual(PigeonState); - }); - - it('returns a map of themes with filter', async () => { - const states = await getThemes(dotLottieAnimation, (file) => file.name.startsWith('states/invalid')); - - expect(states).toEqual({}); - }); -}); - -describe('getAnimations', () => { - it('returns a map of animations', async () => { - const animations = await getAnimations(dotLottieAnimation); - - const expectedAnimations = { - lottie1: dotLottieLottie1 as AnimationData, - }; - - expect(animations).toEqual(expectedAnimations); - }); - - it('returns a map of animations with filter', async () => { - const animations = await getAnimations(dotLottieAnimation, { inlineAssets: false }, (file) => - file.name.startsWith('animations/invalid'), - ); - - expect(animations).toEqual({}); - }); -}); diff --git a/packages/dotlottie-js/src/tests/utils-node.spec.ts b/packages/dotlottie-js/src/tests/utils-node.spec.ts deleted file mode 100644 index 418e3924..00000000 --- a/packages/dotlottie-js/src/tests/utils-node.spec.ts +++ /dev/null @@ -1,371 +0,0 @@ -/** - * Copyright 2023 Design Barn Inc. - */ - -/* eslint-disable @lottiefiles/import-filename-format */ - -import { zipSync, unzipSync } from 'fflate'; - -import type { AnimationData, Manifest } from '..'; -import { - DotLottieError, - ErrorCodes, - dataUrlFromU8, - getAnimation, - getAnimations, - getImage, - getImages, - getManifest, - getTheme, - getThemes, - loadFromURL, - createError, - isValidURL, - getStateMachine, - getStateMachines, - getAudio, - getAllAudio, -} from '../node'; - -import dotLottieAnimationWithAudio from './__fixtures__/audio/2_instrument_animations.lottie'; -import dotLottieAnimation from './__fixtures__/simple/animation.lottie'; -import bullJson from './__fixtures__/simple/animation/animations/bull.json'; -import dotLottieLottie1 from './__fixtures__/simple/animation/animations/lottie1.json'; -import dotLottieManifest from './__fixtures__/simple/animation/manifest.json'; -import dotLottieTheme from './__fixtures__/simple/animation/themes/theme1.json'; -import dotLottieAnimationWithImages from './__fixtures__/simple/big-merged-dotlottie.lottie'; -import bullAnimation from './__fixtures__/simple/bull.lottie'; -import stateAnimation from './__fixtures__/simple/exploding_pigeon.lottie'; -import { PigeonState, PigeonWithoutExplosion } from './__fixtures__/simple/state/pigeon-state'; - -describe('createError', () => { - it('returns an instance of Error with the correct message', () => { - const errorMessage = 'This is an error'; - const error = createError(errorMessage); - - expect(error).toBeInstanceOf(Error); - expect(error.message).toBe(`[dotlottie-js]: ${errorMessage}`); - }); -}); - -describe('isValidURL', () => { - it('returns true for a valid URL', () => { - expect(isValidURL('https://www.valid.com')).toBe(true); - }); - - it('returns false for an invalid URL', () => { - expect(isValidURL('invalid')).toBe(false); - }); -}); - -describe('loadFromUrl', () => { - it('throws when url is not valid', async () => { - await expectAsync(loadFromURL('')).toBeRejectedWith( - new DotLottieError('Invalid url provided for .lottie file', ErrorCodes.INVALID_DOTLOTTIE), - ); - }); - - it('throws an error if invalid content-type is returned', async () => { - spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'text/html' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWith( - new DotLottieError( - 'Invalid content type for .lottie file, expected application/zip or application/octet-stream, received text/html', - ErrorCodes.INVALID_DOTLOTTIE, - ), - ); - }); - - it('loads a dotlottie from a url with content-type application/octet-stream', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'application/octet-stream' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await loadFromURL(dotLottieURL); - - expect(dotLottie).toBeDefined(); - expect(dotLottie).toBeInstanceOf(Uint8Array); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('loads a dotlottie from a url', async () => { - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieAnimation, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const dotLottie = await loadFromURL(dotLottieURL); - - expect(dotLottie).toBeDefined(); - expect(dotLottie).toBeInstanceOf(Uint8Array); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('throws error if dotlottie with no manifest is loaded', async () => { - const data: Record = {}; - - // convert the lottie to uint8array - data['animations/lottie1.json'] = new TextEncoder().encode(JSON.stringify(dotLottieLottie1)); - - const dotLottieWithNoManifest = zipSync(data); - - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieWithNoManifest, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWith( - new DotLottieError('Invalid .lottie file, manifest.json is missing', ErrorCodes.INVALID_DOTLOTTIE), - ); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); - - it('throws error if manifest.json has invalid structure', async () => { - const data: Record = {}; - - data['manifest.json'] = new TextEncoder().encode( - JSON.stringify({ - version: '1.0', - revision: 1, - // invalid keywords type - keywords: 1, - author: 'LottieFiles', - generator: 'dotLottie-js_v2.0', - // animations array is missing - themes: [{ id: 'theme1', animations: ['lottie1'] }], - }), - ); - - const dotLottieWithInvalidManifest = zipSync(data); - - const fetchSpy = spyOn(typeof window === 'undefined' ? global : window, 'fetch').and.returnValue( - Promise.resolve(new Response(dotLottieWithInvalidManifest, { headers: { 'content-type': 'application/zip' } })), - ); - - const dotLottieURL = 'https://lottiefiles.fake/animation/animation.lottie'; - - const error = `Invalid .lottie file, manifest.json structure is invalid, ${JSON.stringify( - { - animations: ['Invalid type'], - keywords: ['Invalid type'], - }, - null, - 2, - )}`; - - await expectAsync(loadFromURL(dotLottieURL)).toBeRejectedWithError(error); - - expect(fetchSpy).toHaveBeenCalledWith(dotLottieURL); - }); -}); - -describe('getManifest', () => { - it('return manifest from dotlottie', async () => { - const manifest = await getManifest(dotLottieAnimation); - - expect(manifest).toEqual(dotLottieManifest as Manifest); - }); - - it('returns undefined if manifest is not found', async () => { - const data: Record = {}; - - // convert the lottie to uint8array - data['animations/lottie1.json'] = new TextEncoder().encode(JSON.stringify(dotLottieLottie1)); - - const dotLottieWithNoManifest = zipSync(data); - - const manifest = await getManifest(dotLottieWithNoManifest); - - expect(manifest).toBeUndefined(); - }); -}); - -describe('getImage', () => { - it('returns undefined if image is not found', async () => { - const image = await getImage(dotLottieAnimation, 'invalid_image'); - - expect(image).toBeUndefined(); - }); -}); - -describe('getAudio', () => { - it('returns the audio', async () => { - const audio = await getAudio(dotLottieAnimationWithAudio, 'audio_1.mpeg'); - - expect(audio?.length).toBeGreaterThan(0); - }); - - it('returns undefined if audio is not found', async () => { - const audio = await getAudio(dotLottieAnimationWithAudio, 'invalid_audio'); - - expect(audio).toBeUndefined(); - }); -}); - -describe('getAnimation', () => { - it('returns undefined if animation not found', async () => { - const animation = await getAnimation(dotLottieAnimation, 'invalid_animation'); - - expect(animation).toBeUndefined(); - }); - - it('get animation by id', async () => { - const animation = await getAnimation(dotLottieAnimation, 'lottie1'); - - expect(animation).toEqual(dotLottieLottie1 as AnimationData); - }); - - it('returns inlined images within the animation', async () => { - const manifest = await getManifest(bullAnimation); - - const animationId = manifest?.animations[0]?.id || ''; - - const animation = await getAnimation(bullAnimation, animationId, { inlineAssets: true }); - - expect(JSON.stringify(animation?.assets)).toEqual(JSON.stringify(bullJson.assets)); - }); -}); - -describe('getTheme', () => { - it('returns undefined if theme not found', async () => { - const theme = await getTheme(dotLottieAnimation, 'invalid_theme'); - - expect(theme).toBeUndefined(); - }); - - it('gets theme by id', async () => { - const theme = await getTheme(dotLottieAnimation, 'theme1'); - - expect(theme).toEqual(dotLottieTheme); - }); -}); - -describe('getStateMachine', () => { - it('returns undefined if state machine is not found', async () => { - const stateMachine = await getStateMachine(stateAnimation, 'invalid_state'); - - expect(stateMachine).toBeUndefined(); - }); - - it('gets state machine by id', async () => { - const stateMachine = await getStateMachine(stateAnimation, 'pigeonWithoutExplosion'); - - expect(stateMachine?.states).toEqual(PigeonWithoutExplosion.states); - }); -}); - -describe('getImages', () => { - it('returns a map of images', async () => { - const images = await getImages(dotLottieAnimationWithImages); - - const unzippedDotLottie = unzipSync(dotLottieAnimationWithImages); - const expectedImages: Record = {}; - - // eslint-disable-next-line guard-for-in - for (const key in unzippedDotLottie) { - const data = unzippedDotLottie[key]; - - if (key.startsWith('images/') && data) { - expectedImages[key.replace('images/', '')] = await dataUrlFromU8(data); - } - } - - expect(images).toEqual(expectedImages); - }); - - it('returns a map of images with filter', async () => { - const images = await getImages(dotLottieAnimationWithImages, (file) => file.name.startsWith('images/invalid')); - - expect(images).toEqual({}); - }); -}); - -describe('getStateMachines', () => { - it('returns a map of state machines', async () => { - const stateMachines = await getStateMachines(stateAnimation); - - expect(JSON.parse(stateMachines['pigeonWithoutExplosion'] ?? '')).toEqual(PigeonWithoutExplosion); - expect(JSON.parse(stateMachines['explodingPigeon'] ?? '')).toEqual(PigeonState); - }); - - it('returns a map of themes with filter', async () => { - const states = await getThemes(dotLottieAnimation, (file) => file.name.startsWith('states/invalid')); - - expect(states).toEqual({}); - }); -}); - -describe('getAllAudio', () => { - it('returns a map of all the audio files', async () => { - const audio = await getAllAudio(dotLottieAnimationWithAudio); - - const unzippedDotLottie = unzipSync(dotLottieAnimationWithAudio); - const expectedAudio: Record = {}; - - // eslint-disable-next-line guard-for-in - for (const key in unzippedDotLottie) { - const data = unzippedDotLottie[key]; - - if (key.startsWith('audio/') && data) { - expectedAudio[key.replace('audio/', '')] = await dataUrlFromU8(data); - } - } - - expect(audio).toEqual(expectedAudio); - }); - - it('returns a map of audio with filter', async () => { - const audio = await getAllAudio(dotLottieAnimationWithAudio, (file) => file.name.startsWith('audio/invalid')); - - expect(audio).toEqual({}); - }); -}); - -describe('getThemes', () => { - it('returns a map of themes', async () => { - const themes = await getThemes(dotLottieAnimation); - - const expectedThemes = { - theme1: dotLottieTheme, - }; - - expect(themes).toEqual(expectedThemes); - }); - - it('returns a map of themes with filter', async () => { - const themes = await getThemes(dotLottieAnimation, (file) => file.name.startsWith('themes/invalid')); - - expect(themes).toEqual({}); - }); -}); - -describe('getAnimations', () => { - it('returns a map of animations', async () => { - const animations = await getAnimations(dotLottieAnimation); - - const expectedAnimations = { - lottie1: dotLottieLottie1 as AnimationData, - }; - - expect(animations).toEqual(expectedAnimations); - }); - - it('returns a map of animations with filter', async () => { - const animations = await getAnimations(dotLottieAnimation, { inlineAssets: false }, (file) => - file.name.startsWith('animations/invalid'), - ); - - expect(animations).toEqual({}); - }); -}); diff --git a/packages/dotlottie-js/src/types.ts b/packages/dotlottie-js/src/types.ts new file mode 100644 index 00000000..2bc82fdc --- /dev/null +++ b/packages/dotlottie-js/src/types.ts @@ -0,0 +1,23 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import type { ZipOptions } from 'fflate'; + +export interface GetAnimationOptions { + inlineAssets?: boolean; +} + +export interface ConversionOptions { + zipOptions?: ZipOptions; +} + +export type AnimationData = AnimationType; + +export interface ExportOptions { + inlineAssets?: boolean; +} + +export type ImageData = string | ArrayBuffer | Blob; +export type AudioData = string | ArrayBuffer | Blob; diff --git a/packages/dotlottie-js/src/common/utils.ts b/packages/dotlottie-js/src/utils.ts similarity index 82% rename from packages/dotlottie-js/src/common/utils.ts rename to packages/dotlottie-js/src/utils.ts index 26003f13..402d2799 100644 --- a/packages/dotlottie-js/src/common/utils.ts +++ b/packages/dotlottie-js/src/utils.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023 Design Barn Inc. + * Copyright 2024 Design Barn Inc. */ /* eslint-disable guard-for-in */ @@ -8,12 +8,10 @@ import type { Animation as AnimationData, Asset } from '@lottie-animation-commun import type { UnzipFileFilter, Unzipped } from 'fflate'; import { unzip as fflateUnzip, strFromU8 } from 'fflate'; import { fileTypeFromBuffer } from 'file-type'; -import { flatten, safeParse } from 'valibot'; -import type { LottieStateMachine } from '../lottie-state-machine'; - -import type { Manifest } from './manifest'; -import { ManifestSchema } from './manifest'; +import type { ManifestV1 } from './v1/common/schemas/manifest'; +import type { LottieStateMachine } from './v2/browser'; +import type { Manifest as ManifestV2 } from './v2/common/schemas'; export enum ErrorCodes { ASSET_NOT_FOUND = 'ASSET_NOT_FOUND', @@ -27,37 +25,11 @@ export class DotLottieError extends Error { public constructor(message: string, code?: ErrorCodes) { super(message); - this.name = '[dotlottie-js]'; + this.name = '[dotLottie-js]'; this.code = code; } } -/** - * Creates an Error object with the specified message. - * - * @remarks - * This function accepts a message string and constructs a new Error object prefixed with "[dotlottie-js]: ". - * - * @deprecated - * This function has been deprecated in favor of using the {@link DotLottieError} class directly. - * - * @param message - The error message to include in the Error object. - * @returns An Error object with the specified message, prefixed with "[dotlottie-js]: ". - * - * @example - * ```typescript - * const message = 'DotLottie not found'; - * const error = createError(message); - * ``` - * - * @public - */ -export const createError = (message: string): Error => { - const error = new Error(`[dotlottie-js]: ${message}`); - - return error; -}; - /** * Converts a base64 string into a Uint8Array. * @@ -89,12 +61,10 @@ export const base64ToUint8Array = (base64String: string): Uint8Array => { return uint8Array; }; -export const getMimeTypeFromBase64 = async (base64: string): Promise => { - const data = base64ToUint8Array(base64); - - const mime = await fileTypeFromBuffer(data); +export const getMimeTypeFromUint8Data = async (file: Uint8Array): Promise => { + const data = await fileTypeFromBuffer(file); - return mime?.mime.toString(); + return data?.mime.toString(); }; /** @@ -114,10 +84,12 @@ export const getMimeTypeFromBase64 = async (base64: string): Promise => { - const data = await fileTypeFromBuffer(file); +export const getMimeTypeFromBase64 = async (base64: string): Promise => { + const data = base64ToUint8Array(base64); - return data?.mime.toString(); + const mime = await fileTypeFromBuffer(data); + + return mime?.mime.toString(); }; /** @@ -269,7 +241,7 @@ export function isAudioAsset(asset: Asset.Value): asset is Asset.Image { * @param dotLottie - The .lottie data as a Uint8Array. * @param filter - The filter function to apply to the files. Defaults to a function that always returns true. * @returns A Promise that resolves with the unzipped data. - * @throws {@link DotLottieError} if the .lottie data is not provided or is invalid. + * @throws {@link dotLottieError} if the .lottie data is not provided or is invalid. * * @example * ```typescript @@ -284,7 +256,7 @@ export async function unzipDotLottie( filter: UnzipFileFilter = (): boolean => true, ): Promise { if (!(dotLottie instanceof Uint8Array)) { - throw new DotLottieError('DotLottie not found', ErrorCodes.INVALID_DOTLOTTIE); + throw new DotLottieError('dotLottie not found', ErrorCodes.INVALID_DOTLOTTIE); } const unzipped = await new Promise((resolve, reject) => { @@ -313,7 +285,7 @@ export async function unzipDotLottie( * Accepts a file object and returns a boolean indicating whether the file should be included. * @returns A `Promise` that resolves to the `Uint8Array` of the unzipped target file. * - * @throws {@link DotLottieError} if the input is not a valid `.lottie` file or if the target file is not found. + * @throws {@link dotLottieError} if the input is not a valid `.lottie` file or if the target file is not found. * * @example * ```typescript @@ -330,7 +302,7 @@ export async function unzipDotLottieFile( filter?: UnzipFileFilter, ): Promise { if (!(dotLottie instanceof Uint8Array)) { - throw new DotLottieError('DotLottie not found', ErrorCodes.INVALID_DOTLOTTIE); + throw new DotLottieError('Invalid dotLottie', ErrorCodes.INVALID_DOTLOTTIE); } const unzipped = await unzipDotLottie(dotLottie, (file) => file.name === path && (!filter || filter(file))); @@ -339,14 +311,14 @@ export async function unzipDotLottieFile( } /** - * Retrieves the manifest data from the given DotLottie object. + * Retrieves the manifest data from the given dotLottie object. * * @remarks - * This function accepts a DotLottie object as a Uint8Array and extracts the manifest data from it. + * This function accepts a dotLottie object as a Uint8Array and extracts the manifest data from it. * The manifest contains metadata information about the .lottie file, such as the list of animations, themes, and image assets. * It returns a Promise that resolves to the manifest data or `undefined` if the manifest is not found. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @returns A Promise that resolves with the manifest data or `undefined` if not found. * * @example @@ -357,7 +329,7 @@ export async function unzipDotLottieFile( * * @public */ -export async function getManifest(dotLottie: Uint8Array): Promise { +export async function getManifest(dotLottie: Uint8Array): Promise { const manifestFileName = 'manifest.json'; const unzipped = await unzipDotLottie(dotLottie, (file) => file.name === manifestFileName); @@ -368,48 +340,42 @@ export async function getManifest(dotLottie: Uint8Array): Promise { + const manifest = await getManifest(dotLottie); + + return manifest?.version ?? '1.0.0'; } /** - * Validates the provided DotLottie data. + * Validates the provided dotLottie data. * * @remarks * This function accepts a Uint8Array containing .lottie data and validates its structure and content. * It returns a Promise that resolves with an object containing a success boolean and an optional error string. * - * @param dotLottie - The DotLottie data as a Uint8Array. + * @param dotLottie - The dotLottie data as a Uint8Array. * @returns A Promise that resolves with an object containing a success boolean and an optional error string. * * @example * ```typescript * const dotLottie = new Uint8Array(...); - * const validationResult = await validateDotLottie(dotLottie); + * const validationResult = await validatedotLottie(dotLottie); * ``` * * @public */ export async function validateDotLottie(dotLottie: Uint8Array): Promise<{ error?: string; success: boolean }> { if (!(dotLottie instanceof Uint8Array)) { - return { success: false, error: 'DotLottie not found' }; + return { success: false, error: 'dotLottie not found' }; } const manifest = await getManifest(dotLottie); if (typeof manifest === 'undefined') { - return { success: false, error: 'Invalid .lottie file, manifest.json is missing' }; - } - - const manifestValidationResult = safeParse(ManifestSchema, manifest); - - if (!manifestValidationResult.success) { - const error = `Invalid .lottie file, manifest.json structure is invalid, ${JSON.stringify( - flatten(manifestValidationResult.error).nested, - null, - 2, - )}`; - - return { success: false, error }; + return { success: false, error: 'manifest.json is missing' }; } return { success: true }; @@ -420,11 +386,11 @@ export async function validateDotLottie(dotLottie: Uint8Array): Promise<{ error? * * @remarks * This function takes an ArrayBuffer containing .lottie data and converts it into a Uint8Array. - * It validates the data and returns a Promise that resolves with the DotLottie data as a Uint8Array. + * It validates the data and returns a Promise that resolves with the dotLottie data as a Uint8Array. * * @param arrayBuffer - The ArrayBuffer containing .lottie data. - * @returns A Promise that resolves with the DotLottie data as a Uint8Array. - * @throws {@link DotLottieError} if the data is invalid. + * @returns A Promise that resolves with the dotLottie data as a Uint8Array. + * @throws {@link dotLottieError} if the data is invalid. * * @example * ```typescript @@ -451,11 +417,11 @@ export async function loadFromArrayBuffer(arrayBuffer: ArrayBuffer): Promise { const arrayBuffer = await response.arrayBuffer(); - const contentType = response.headers.get('content-type'); + const zipMagicHeader = new Uint8Array([0x50, 0x4b, 0x03, 0x04]); + + const header = new Uint8Array(arrayBuffer.slice(0, 4)); - if (!['application/zip', 'application/octet-stream'].includes(contentType || '')) { - throw new DotLottieError( - `Invalid content type for .lottie file, expected application/zip or application/octet-stream, received ${contentType}`, - ErrorCodes.INVALID_DOTLOTTIE, - ); + if (!header.every((value, index) => value === zipMagicHeader[index])) { + throw new DotLottieError('Invalid .lottie file', ErrorCodes.INVALID_DOTLOTTIE); } const dotLottie = await loadFromArrayBuffer(arrayBuffer); @@ -489,13 +454,13 @@ export async function loadFromURL(src: string): Promise { } /** - * Retrieves an audio from the given DotLottie object by its filename. + * Retrieves an audio from the given dotLottie object by its filename. * * @remarks - * This function accepts a DotLottie object as a Uint8Array, the filename of the audio to retrieve, and an optional filter function. + * This function accepts a dotLottie object as a Uint8Array, the filename of the audio to retrieve, and an optional filter function. * It returns a Promise that resolves to the audio data URL or `undefined` if not found. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param filename - The filename of the image to get. * @param filter - An optional filter function to apply on the unzipping process. * @returns A Promise that resolves with the audio data URL or `undefined` if not found. @@ -514,7 +479,15 @@ export async function getAudio( filename: string, filter?: UnzipFileFilter, ): Promise { - const audioFilename = `audio/${filename}`; + const version = await getDotLottieVersion(dotLottie); + + let audioPath = 'audio/'; + + if (version === '2') { + audioPath = 'u/'; + } + + const audioFilename = `${audioPath}${filename}`; const unzipped = await unzipDotLottieFile(dotLottie, audioFilename, filter); @@ -526,13 +499,13 @@ export async function getAudio( } /** - * Retrieves all audio files from the given DotLottie object. + * Retrieves all audio files from the given dotLottie object. * * @remarks - * This function accepts a DotLottie object as a Uint8Array and an optional filter function to further refine the extraction. + * This function accepts a dotLottie object as a Uint8Array and an optional filter function to further refine the extraction. * It returns a Promise that resolves to a record containing the audio data URLs mapped by their ID. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param filter - An optional filter function to apply on the unzipping process. * @returns A Promise that resolves to a record containing the audio data URLs mapped by their ID. * @@ -545,19 +518,27 @@ export async function getAudio( * @public */ export async function getAllAudio(dotLottie: Uint8Array, filter?: UnzipFileFilter): Promise> { + const version = await getDotLottieVersion(dotLottie); + + let audioPath = 'audio/'; + + if (version === '2') { + audioPath = 'u/'; + } + const unzippedAudio = await unzipDotLottie(dotLottie, (file) => { - const name = file.name.replace('audio/', ''); + const name = file.name.replace(audioPath, ''); - return file.name.startsWith('audio/') && (!filter || filter({ ...file, name })); + return file.name.startsWith(audioPath) && (!filter || filter({ ...file, name })); }); const audio: Record = {}; - for (const audioPath in unzippedAudio) { - const unzippedSingleAudio = unzippedAudio[audioPath]; + for (const audioFilename in unzippedAudio) { + const unzippedSingleAudio = unzippedAudio[audioFilename]; if (unzippedSingleAudio instanceof Uint8Array) { - const audioId = audioPath.replace('audio/', ''); + const audioId = audioFilename.replace(audioPath, ''); audio[audioId] = await dataUrlFromU8(unzippedSingleAudio); } @@ -567,14 +548,14 @@ export async function getAllAudio(dotLottie: Uint8Array, filter?: UnzipFileFilte } /** - * Inlines audio assets for the given animations within a DotLottie object. + * Inlines audio assets for the given animations within a dotLottie object. * * @remarks - * This function accepts a DotLottie object as a Uint8Array and a record containing the animations to process. + * This function accepts a dotLottie object as a Uint8Array and a record containing the animations to process. * It identifies the audio used in the animations and replaces their references with the actual audio data. * This operation is performed asynchronously, and the function returns a Promise that resolves when the operation is complete. * - * @param dotLottie - The DotLottie object containing the animations. + * @param dotLottie - The dotLottie object containing the animations. * @param animations - A record containing the animations to process. * @returns A Promise that resolves when the operation is complete, returning nothing. * @@ -628,13 +609,13 @@ export async function inlineAudioAssets( } /** - * Retrieves an image from the given DotLottie object by its filename. + * Retrieves an image from the given dotLottie object by its filename. * * @remarks - * This function accepts a DotLottie object as a Uint8Array, the filename of the image to retrieve, and an optional filter function. + * This function accepts a dotLottie object as a Uint8Array, the filename of the image to retrieve, and an optional filter function. * It returns a Promise that resolves to the image data URL or `undefined` if not found. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param filename - The filename of the image to get. * @param filter - An optional filter function to apply on the unzipping process. * @returns A Promise that resolves with the image data URL or `undefined` if not found. @@ -653,7 +634,15 @@ export async function getImage( filename: string, filter?: UnzipFileFilter, ): Promise { - const imageFilename = `images/${filename}`; + const version = await getDotLottieVersion(dotLottie); + + let imagesPath = 'images/'; + + if (version === '2') { + imagesPath = 'i/'; + } + + const imageFilename = `${imagesPath}${filename}`; const unzipped = await unzipDotLottieFile(dotLottie, imageFilename, filter); @@ -665,13 +654,13 @@ export async function getImage( } /** - * Retrieves all images from the given DotLottie object. + * Retrieves all images from the given dotLottie object. * * @remarks - * This function accepts a DotLottie object as a Uint8Array and an optional filter function to further refine the extraction. + * This function accepts a dotLottie object as a Uint8Array and an optional filter function to further refine the extraction. * It returns a Promise that resolves to a record containing the image data URLs mapped by their ID. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param filter - An optional filter function to apply on the unzipping process. * @returns A Promise that resolves to a record containing the image data URLs mapped by their ID. * @@ -684,10 +673,18 @@ export async function getImage( * @public */ export async function getImages(dotLottie: Uint8Array, filter?: UnzipFileFilter): Promise> { + const version = await getDotLottieVersion(dotLottie); + + let imagesPath = 'images/'; + + if (version === '2') { + imagesPath = 'i/'; + } + const unzippedImages = await unzipDotLottie(dotLottie, (file) => { - const name = file.name.replace('images/', ''); + const name = file.name.replace(imagesPath, ''); - return file.name.startsWith('images/') && (!filter || filter({ ...file, name })); + return file.name.startsWith(imagesPath) && (!filter || filter({ ...file, name })); }); const images: Record = {}; @@ -696,7 +693,7 @@ export async function getImages(dotLottie: Uint8Array, filter?: UnzipFileFilter) const unzippedImage = unzippedImages[imagePath]; if (unzippedImage instanceof Uint8Array) { - const imageId = imagePath.replace('images/', ''); + const imageId = imagePath.replace(imagePath, ''); images[imageId] = await dataUrlFromU8(unzippedImage); } @@ -706,14 +703,14 @@ export async function getImages(dotLottie: Uint8Array, filter?: UnzipFileFilter) } /** - * Inlines image assets for the given animations within a DotLottie object. + * Inlines image assets for the given animations within a dotLottie object. * * @remarks - * This function accepts a DotLottie object as a Uint8Array and a record containing the animations to process. + * This function accepts a dotLottie object as a Uint8Array and a record containing the animations to process. * It identifies the images used in the animations and replaces their references with the actual image data. * This operation is performed asynchronously, and the function returns a Promise that resolves when the operation is complete. * - * @param dotLottie - The DotLottie object containing the animations. + * @param dotLottie - The dotLottie object containing the animations. * @param animations - A record containing the animations to process. * @returns A Promise that resolves when the operation is complete, returning nothing. * @@ -767,13 +764,13 @@ export async function inlineImageAssets( } /** - * Retrieves an animation from the given DotLottie object by its ID. + * Retrieves an animation from the given dotLottie object by its ID. * * @remarks - * This function accepts a DotLottie object as a Uint8Array, the animation ID to retrieve, and an optional inlineAssets option. + * This function accepts a dotLottie object as a Uint8Array, the animation ID to retrieve, and an optional inlineAssets option. * It returns a Promise that resolves to the animation data or `undefined` if not found. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param animationId - The animation ID to get. * @param options - An object containing an optional `inlineAssets` boolean to control whether image assets should be inlined. * @param filter - An optional function to filter the files to be unzipped. @@ -820,13 +817,13 @@ export async function getAnimation( } /** - * Retrieves the animations from the given DotLottie object, with optional filtering and asset inlining. + * Retrieves the animations from the given dotLottie object, with optional filtering and asset inlining. * * @remarks - * This function accepts a DotLottie object as a Uint8Array, an optional inlineAssets option, and an optional filter function. + * This function accepts a dotLottie object as a Uint8Array, an optional inlineAssets option, and an optional filter function. * It returns a Promise that resolves to a record containing the animation data mapped by their ID. * - * @param dotLottie - The Uint8Array of DotLottie data. + * @param dotLottie - The Uint8Array of dotLottie data. * @param options - An object containing an optional `inlineAssets` boolean to control whether assets should be inlined. * @param filter - An optional function to filter the files to be unzipped. * @returns A Promise that resolves to a record containing the animation data mapped by their ID. @@ -845,17 +842,26 @@ export async function getAnimations( filter?: UnzipFileFilter, ): Promise> { const animationsMap: Record = {}; + + const version = await getDotLottieVersion(dotLottie); + + let animationsPath = 'animations/'; + + if (version === '2') { + animationsPath = 'a/'; + } + const unzippedAnimations = await unzipDotLottie(dotLottie, (file) => { - const filename = file.name.replace('animations/', '').replace('.json', ''); + const filename = file.name.replace(animationsPath, '').replace('.json', ''); - return file.name.startsWith('animations/') && (!filter || filter({ ...file, name: filename })); + return file.name.startsWith(animationsPath) && (!filter || filter({ ...file, name: filename })); }); for (const animationPath in unzippedAnimations) { const data = unzippedAnimations[animationPath]; if (data instanceof Uint8Array) { - const animationId = animationPath.replace('animations/', '').replace('.json', ''); + const animationId = animationPath.replace(animationsPath, '').replace('.json', ''); const animationData = JSON.parse(strFromU8(data, false)) as AnimationData; animationsMap[animationId] = animationData; @@ -895,16 +901,16 @@ export async function getThemes( const themesMap: Record> = {}; const unzippedThemes = await unzipDotLottie(dotLottie, (file) => { - const name = file.name.replace('themes/', '').replace('.json', ''); + const name = file.name.replace('t/', '').replace('.json', ''); - return file.name.startsWith('themes/') && (!filter || filter({ ...file, name })); + return file.name.startsWith('t/') && (!filter || filter({ ...file, name })); }); for (const themePath in unzippedThemes) { const data = unzippedThemes[themePath]; if (data instanceof Uint8Array) { - const themeId = themePath.replace('themes/', '').replace('.json', ''); + const themeId = themePath.replace('t/', '').replace('.json', ''); themesMap[themeId] = JSON.parse(strFromU8(data, false)); } @@ -937,7 +943,7 @@ export async function getTheme( themeId: string, filter?: UnzipFileFilter, ): Promise | undefined> { - const themeFilename = `themes/${themeId}.json`; + const themeFilename = `t/${themeId}.json`; const unzippedTheme = await unzipDotLottieFile(dotLottie, themeFilename, filter); @@ -972,16 +978,16 @@ export async function getStateMachines( const statesMap: Record = {}; const unzippedStates = await unzipDotLottie(dotLottie, (file) => { - const name = file.name.replace('states/', '').replace('.json', ''); + const name = file.name.replace('s/', '').replace('.json', ''); - return file.name.startsWith('states/') && (!filter || filter({ ...file, name })); + return file.name.startsWith('s/') && (!filter || filter({ ...file, name })); }); for (const statePath in unzippedStates) { const data = unzippedStates[statePath]; if (data instanceof Uint8Array) { - const themeId = statePath.replace('states/', '').replace('.json', ''); + const themeId = statePath.replace('s/', '').replace('.json', ''); statesMap[themeId] = strFromU8(data, false); } @@ -1014,7 +1020,7 @@ export async function getStateMachine( stateMachineId: string, filter?: UnzipFileFilter, ): Promise { - const stateMachineFilename = `states/${stateMachineId}.json`; + const stateMachineFilename = `s/${stateMachineId}.json`; const unzippedStateMachine = await unzipDotLottieFile(dotLottie, stateMachineFilename, filter); diff --git a/packages/dotlottie-js/src/v1/__tests__/browser/animation.spec.ts b/packages/dotlottie-js/src/v1/__tests__/browser/animation.spec.ts new file mode 100644 index 00000000..ac843518 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/browser/animation.spec.ts @@ -0,0 +1,290 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable no-new */ +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import type { AnimationOptionsV1 as AnimationOptions } from '../../index.browser'; +import { LottieAnimationV1 } from '../../index.browser'; + +test('throws an error if it receives an invalid id when constructed', () => { + expect(() => new LottieAnimationV1({ id: '' } as unknown as AnimationOptions)).toThrow('Invalid animation id'); +}); + +test('throws an error if it receives an invalid url when constructed', () => { + const invalidUrl = 'xyz'; + + expect(() => { + new LottieAnimationV1({ id: 'test', url: invalidUrl }); + }).toThrow('Invalid animation url'); +}); + +test('throws an error if it receives an invalid lottie data when constructed', () => { + const invalidData = {} as AnimationType; + + expect(() => { + new LottieAnimationV1({ id: 'test', data: invalidData }); + }).toThrow('Received invalid Lottie data.'); +}); + +test('throws an error if it receives an no data or url when constructed', () => { + expect(() => { + new LottieAnimationV1({ id: 'test' } as unknown as AnimationOptions); + }).toThrow('No data or url provided.'); +}); + +test('gets and sets the zipOptions', () => { + const animation = new LottieAnimationV1({ + id: 'test', + data: animationData as unknown as AnimationType, + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(animation.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + animation.zipOptions = { + level: 1, + }; + + expect(animation.zipOptions).toEqual({ + level: 1, + }); +}); + +test('gets and sets the id', () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.id).toEqual('test'); + + animation.id = 'test2'; + + expect(animation.id).toEqual('test2'); +}); + +test('gets and sets the data', () => { + const animation = new LottieAnimationV1({ id: 'test', url: 'https://example.com' }); + + expect(animation.data).toBeUndefined(); + + animation.data = animationData as unknown as AnimationType; + + expect(animation.data).toEqual(animationData as unknown as AnimationType); +}); + +test('gets and sets the url', () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.url).toBeUndefined(); + + animation.url = 'https://example.com'; + + expect(animation.url).toEqual('https://example.com'); +}); + +describe('toJSON', () => { + test('returns the animation data as a JSON object', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const jsonData = await animation.toJSON(); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + }); + + test('returns the animation with inlined data as a JSON object', async () => { + const animation = new LottieAnimationV1({ + id: 'test', + data: structuredClone(BULL_DATA) as unknown as unknown as AnimationType, + }); + + const jsonData = await animation.toJSON({ inlineAssets: true }); + + expect(jsonData).toEqual(BULL_DATA as unknown as unknown as AnimationType); + }); + + test('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const jsonData = await animation.toJSON(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toJSON()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBase64', () => { + test('returns the base64 of the animation', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const dataUrl = await animation.toBase64(); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + }); + + test('resolves the animation data from the provided url and returns the animation data as a data url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const dataUrl = await animation.toBase64(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toBase64()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBlob', () => { + test('returns the animation data as a blob', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as a blob', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', + }); + + await expect(animation.toBlob()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toArrayBuffer', () => { + test('returns the animation data as an array buffer', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toArrayBuffer()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/browser/audio.spec.ts b/packages/dotlottie-js/src/v1/__tests__/browser/audio.spec.ts new file mode 100644 index 00000000..505da8bc --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/browser/audio.spec.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType, Asset } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; + +import AUDIO_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/audio/instruments_1.json'; +import AUDIO_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/audio/instruments_2.json'; +import { isAudioAsset } from '../../../utils'; +import { DotLottieV1, LottieAudioV1 } from '../../index.browser'; + +describe('LottieAudioV1', () => { + it('gets and sets the zipOptions', () => { + const theme = new LottieAudioV1({ + id: 'audio_1', + fileName: 'audio.mp3', + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(theme.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + theme.zipOptions = { + level: 1, + }; + + expect(theme.zipOptions).toEqual({ + level: 1, + }); + }); + + it('Adds two instrument animations via data.', async () => { + await new DotLottieV1() + .addAnimation({ + id: 'animation_1', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(AUDIO_ANIMATION_2_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottieV1) => { + const audio = value.getAudio(); + + expect(audio.length).toBe(6); + + const expectedData: string[] = []; + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + for (let i = 0; i < audio.length; i += 1) { + expect(await audio[i]?.toDataURL()).toEqual(expectedData[i]); + } + }); + }); + + it('Adds identical instrument animation twice via data.', async () => { + await new DotLottieV1() + .addAnimation({ + id: 'animation_1', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottieV1) => { + const audio = value.getAudio(); + + expect(audio.length).toBe(6); + + const expectedData: string[] = []; + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + for (let i = 0; i < audio.length; i += 1) { + expect(await audio[i]?.toDataURL()).toEqual(expectedData[i % 3]); + } + }); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/browser/dotlottie.spec.ts b/packages/dotlottie-js/src/v1/__tests__/browser/dotlottie.spec.ts new file mode 100644 index 00000000..5f2e7c4a --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/browser/dotlottie.spec.ts @@ -0,0 +1,854 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { unzipSync } from 'fflate'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import pkg from '../../../../package.json'; +import bullData from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import dotlottieAnimation from '../../../__tests__/__fixtures__/simple/animation.lottie?arraybuffer'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import manifest from '../../../__tests__/__fixtures__/simple/animation/manifest.json'; +import bigMergedDotLottie from '../../../__tests__/__fixtures__/simple/big-merged-dotlottie.lottie?arraybuffer'; +import editedDotlottieAnimation from '../../../__tests__/__fixtures__/simple/edited-settings.lottie?arraybuffer'; +import editedAnimationData from '../../../__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json'; +import editedManifest from '../../../__tests__/__fixtures__/simple/edited-settings/manifest.json'; +import type { AnimationData } from '../../../types'; +import type { AnimationOptionsV1 as AnimationOptions, ManifestAnimationV1, ManifestV1 } from '../../index.browser'; +import { DotLottieV1 as DotLottie, LottieAnimationV1 as LottieAnimation, PlayMode } from '../../index.browser'; + +describe('setAuthor', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setAuthor('Design Barn'); + + expect(result).toBe(dotlottie); + }); + + test('sets the author', () => { + const dotlottie = new DotLottie(); + + dotlottie.setAuthor('Design Barn'); + + expect(dotlottie.author).toBe('Design Barn'); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setAuthor(''); + + expect(dotlottie.author).toBe(''); + }); +}); + +describe('setRevision', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setRevision(1); + + expect(result).toBe(dotlottie); + }); + + test('sets the revision', () => { + const dotlottie = new DotLottie(); + + const revision = 1.5; + + dotlottie.setRevision(revision); + + expect(dotlottie.revision).toBe(revision); + }); +}); + +describe('setDescription', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setDescription('A description'); + + expect(result).toBe(dotlottie); + }); + + test('sets the description', () => { + const dotlottie = new DotLottie(); + + dotlottie.setDescription('A description'); + + expect(dotlottie.description).toBe('A description'); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setDescription(''); + + expect(dotlottie.description).toBe(''); + }); +}); + +describe('generator', () => { + test('has proper generator when no input provided', () => { + const dotLottie = new DotLottie(); + + expect(dotLottie.generator).toBe(`${pkg.name}@${pkg.version}`); + }); +}); + +describe('setRevision', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setRevision(1); + + expect(result).toBe(dotlottie); + }); + + test('sets the revision', () => { + const dotlottie = new DotLottie(); + + const revision = 1.5; + + dotlottie.setRevision(revision); + + expect(dotlottie.revision).toBe(revision); + }); +}); + +describe('setKeywords', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setKeywords('animation, design, lottie'); + + expect(result).toBe(dotlottie); + }); + + test('sets the keywords', () => { + const dotlottie = new DotLottie(); + + const keywords = 'animation, design, lottie'; + + dotlottie.setKeywords(keywords); + + expect(dotlottie.keywords).toBe(keywords); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setKeywords(''); + + expect(dotlottie.keywords).toBe(''); + }); +}); + +describe('addAnimation', () => { + test('throws an error if it receives a duplicate id when constructed', () => { + expect(() => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + }).toThrow('Duplicate animation id detected, aborting.'); + }); + + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('adds an animation', () => { + const animationId = manifest.animations[0]?.id as string; + + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: animationId, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + const animation = dotlottie.animations[0]; + + expect(animation?.id).toBe(manifest.animations[0]?.id); + }); + + test('adds an animation using all customizable options', async () => { + const animationId = 'test_animation'; + + const dotlottie = new DotLottie(); + + const animationOptions: AnimationOptions = { + id: animationId, + data: animationData as unknown as AnimationType, + autoplay: true, + direction: -1, + hover: true, + intermission: 1000, + loop: true, + playMode: PlayMode.Bounce, + speed: 1.5, + }; + + const manifestDataToCompare: ManifestAnimationV1 = { + id: animationId, + autoplay: true, + direction: -1, + hover: true, + intermission: 1000, + loop: true, + playMode: PlayMode.Bounce, + speed: 1.5, + }; + + dotlottie.addAnimation({ + ...animationOptions, + }); + + await dotlottie.build(); + + const animationManifest = dotlottie.manifest.animations[0]; + + expect(animationManifest).toEqual(manifestDataToCompare); + }); +}); + +describe('removeAnimation', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('removes an animation', () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + dotlottie.removeAnimation(manifest.animations[0]?.id as string); + + expect(dotlottie.animations.length).toBe(0); + }); +}); + +describe('getAnimation', () => { + test('returns animation instance', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); + + expect(animation).toBeInstanceOf(LottieAnimation); + + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(animationData as unknown as AnimationType); + }); + + test('returns undefined if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + const animation = await dotlottie.getAnimation('non_existent_animation'); + + expect(animation).toBeUndefined(); + }); + + test('returns animation instance with inlined assets', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: structuredClone(bullData) as unknown as AnimationData, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); + + expect(animation).toBeInstanceOf(LottieAnimation); + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(bullData as unknown as AnimationData); + }); + + test('adds multiple animations and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); + + dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, + }); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); + + test('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); + + await dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }) + .build(); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); +}); + +describe('download', () => { + test('throws error on node environment', async () => { + // skip test if running in browser environment + if (typeof window !== 'undefined') return; + + const dotlottie = new DotLottie(); + + await expect( + await dotlottie + .addAnimation({ + id: 'test_animation', + data: animationData as unknown as AnimationType, + }) + .download('file'), + ).rejects.toThrow('Cannot download dotlottie in a non-browser environment'); + }); + + test('downloads dotlottie file on browser', async () => { + // skip test if running in node environment + if (typeof window === 'undefined') return; + + const dotlottie = new DotLottie(); + + const fileName = 'test.lottie'; + + const fakeLink = document.createElement('a'); + + const clickSpy = vi.spyOn(fakeLink, 'click').mockImplementation(() => { + // do nothing + }); + + vi.spyOn(document, 'createElement').mockImplementation(() => { + return fakeLink; + }); + + const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL'); + + await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }) + .build(); + + await dotlottie.download(fileName); + + const blob = await dotlottie.toBlob(); + + expect(createObjectURLSpy).toHaveBeenCalledTimes(1); + expect(createObjectURLSpy).toHaveBeenCalledWith(blob); + + expect(fakeLink.download).toBe(fileName); + expect(fakeLink.style.display).toBe('none'); + expect(clickSpy).toHaveBeenCalledTimes(1); + }); +}); + +describe('toBlob', () => { + test('returns a blob', async () => { + const dotlottie = new DotLottie(); + + const blob = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + // const arrayBuffer = await blob.arrayBuffer(); + + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotlottie = new DotLottie(); + + const blob1 = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob({ + zipOptions: { + level: 9, + }, + }); + + const blob2 = await dotlottie.toBlob({ + zipOptions: { + level: 0, + }, + }); + + expect(blob1).toBeInstanceOf(Blob); + expect(blob2).toBeInstanceOf(Blob); + + const arrayBuffer1 = await blob1.arrayBuffer(); + const arrayBuffer2 = await blob2.arrayBuffer(); + + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toArrayBuffer', () => { + test('returns an array buffer', async () => { + const dotlottie = new DotLottie(); + + const arrayBuffer = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const arrayBuffer1 = await dotLottie1 + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer({ + zipOptions: { + level: 9, + }, + }); + + const arrayBuffer2 = await dotLottie1.toArrayBuffer({ + zipOptions: { + level: 0, + }, + }); + + expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toBase64', () => { + test('returns base64 string', async () => { + const dotlottie = new DotLottie(); + + const dataURL = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64(); + + const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; + + expect(actualArrayBuffer).toBeInstanceOf(ArrayBuffer); + + const actualContent = unzipSync(new Uint8Array(actualArrayBuffer)); + + expect(Object.keys(actualContent).length).toBeGreaterThan(0); + expect(actualContent[`animations/${manifest.animations[0]?.id}.json`]).toBeDefined(); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const dataURL1 = await dotLottie1 + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64({ + zipOptions: { + level: 9, + }, + }); + + const dataURL2 = await dotLottie1.toBase64({ + zipOptions: { + level: 0, + }, + }); + + const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; + const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; + + // expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); + }); +}); + +describe('fromURL', () => { + test('throws an error if the URL is invalid', async () => { + const dotLottie = new DotLottie(); + + await expect(dotLottie.fromURL('invalid-url')).rejects.toThrow('Invalid URL'); + }); + + test('loads a dotlottie file from a URL', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(dotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + const dotLottie = await new DotLottie().fromURL(animationURL); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotLottie.animations.length).toBe(1); + expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotLottie.manifest).toEqual(manifest as ManifestV1); + + fetchSpy.mockRestore(); + }); + + test('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(editedDotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual(editedManifest as ManifestV1); + + fetchSpy.mockRestore(); + }); +}); + +describe('fromArrayBuffer', () => { + test('loads a dotlottie file from an array buffer', async () => { + const arrayBuffer = dotlottieAnimation; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual(manifest as ManifestV1); + }); + + test('loads a dotLottie containing images from an array buffer', async () => { + const arrayBuffer = bigMergedDotLottie; + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(6); + expect(dotlottie.getImages().length).toBe(16); + expect(dotlottie.animations[0]?.id).toEqual('v1'); + expect(dotlottie.animations[1]?.id).toEqual('v2'); + expect(dotlottie.animations[2]?.id).toEqual('v3'); + expect(dotlottie.animations[3]?.id).toEqual('v4'); + expect(dotlottie.animations[4]?.id).toEqual('v5'); + expect(dotlottie.animations[5]?.id).toEqual('v6'); + expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); + }); +}); + +describe('imageAssets', () => { + test('Adds the Bull animation and checks number of images.', async () => { + const dotlottie = await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(bullData) as unknown as AnimationData, + }) + .build(); + + const animation1 = await dotlottie.getAnimation('animation_1'); + + expect(animation1?.imageAssets.length).toBe(5); + }); +}); + +describe('merge', () => { + test('merges two dotlottie files', async () => { + const dotlottie1 = new DotLottie().addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }); + + const dotlottie2 = new DotLottie().addAnimation({ + id: 'lottie2', + data: animationData as unknown as AnimationType, + }); + + const dotlottie3 = new DotLottie().addAnimation({ + id: 'lottie3', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const dotlottie4 = new DotLottie().addAnimation({ + id: 'lottie4', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const shrekVariant1 = new DotLottie().addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }); + + const shrekVariant2 = new DotLottie().addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }); + + const shrekVariant3 = new DotLottie().addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }); + + const shrekVariant4 = new DotLottie().addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }); + + const shrekVariant5 = new DotLottie().addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }); + + const shrekVariant6 = new DotLottie().addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }); + + const [mergedImageLottie] = await Promise.all([ + new DotLottie().merge(dotlottie3, dotlottie4), + new DotLottie().merge(dotlottie3, dotlottie4).build(), + ]); + + const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ + new DotLottie() + .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) + .build(), + new DotLottie().merge(dotlottie1, dotlottie2).build(), + ]); + + expect(mergedImageLottie.animations.length).toBe(2); + + expect(bigMergedImageLottie.animations.length).toBe(6); + + expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ + 'v1', + 'v2', + 'v3', + 'v4', + 'v5', + 'v6', + ]); + + expect(mergedDotlottie).toBeInstanceOf(DotLottie); + + expect(mergedDotlottie.animations.length).toBe(2); + + expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); + expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); + expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); + }); +}); + +describe('build', () => { + test('it resolves lottie animations', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation.json'; + + const dotlottie = new DotLottie().addAnimation({ + url: animationURL, + id: 'lottie1', + }); + + expect(dotlottie.animations[0]?.data).toBeUndefined(); + + await dotlottie.build(); + + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/browser/image.spec.ts b/packages/dotlottie-js/src/v1/__tests__/browser/image.spec.ts new file mode 100644 index 00000000..d49af2b3 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/browser/image.spec.ts @@ -0,0 +1,321 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import DUPES_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import AUDIO_TEST from '../../../__tests__/__fixtures__/mimetype-tests/mp-3-test.txt?raw'; +import SVG_XML_TEST from '../../../__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt?raw'; +import VIDEO_DOTLOTTIE from '../../../__tests__/__fixtures__/simple/video-embedded.lottie?arraybuffer'; +import { getMimeTypeFromBase64 } from '../../../utils'; +import { DotLottieV1 as DotLottie, LottieImageV1 as LottieImage } from '../../index.browser'; + +describe('LottieImage', () => { + it('gets and sets the zipOptions', () => { + const theme = new LottieImage({ + id: 'image_1', + lottieAssetId: 'image_1', + fileName: 'image_1.png', + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(theme.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + theme.zipOptions = { + level: 1, + }; + + expect(theme.zipOptions).toEqual({ + level: 1, + }); + }); + + it('Adds two bull animations via data.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.png', + 'image_1.png', + 'image_2.png', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + }); + }); + + it('Disables image duplicate detection.', async () => { + await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + expect(value.getImages().length).toBe(10); + }); + }); + + it('Adds two bull animations, one via data the other via URL.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.png', + 'image_1.png', + 'image_2.png', + 'image_3.png', + 'image_4.png', + ]); + }); + }); + + it('Adds an animation with lots of duplicate images.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(DUPES_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(4); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); + }); + }); + + it('Adds an animation with lots of duplicate images but disables image duplicate detection.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: false }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(DUPES_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_2.jpeg', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + }); + }); + + it('Adds multiple variants of an animation with duplicate images.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_0', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_3.png', + 'image_4.png', + 'image_1_1.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_3', + 'image_4', + 'image_1_1', + ]); + }); + }); + + it('getMimeTypeFromBase64 Properly detects mimetype of images.', async () => { + const jpegFormat = await getMimeTypeFromBase64( + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAZABkAAD/2wCEABQQEBkSGScXFycyJh8mMi4mJiYmLj41NTU1NT5EQUFBQUFBREREREREREREREREREREREREREREREREREREREQBFRkZIBwgJhgYJjYmICY2RDYrKzZERERCNUJERERERERERERERERERERERERERERERERERERERERERERERERERP/AABEIAAEAAQMBIgACEQEDEQH/xABMAAEBAAAAAAAAAAAAAAAAAAAABQEBAQAAAAAAAAAAAAAAAAAABQYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJQA9Yv/2Q==', + ); + + expect(jpegFormat).toEqual('image/jpeg'); + + const pngFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC', + ); + + expect(pngFormat).toEqual('image/png'); + + const gifFormat = await getMimeTypeFromBase64( + 'data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs=', + ); + + expect(gifFormat).toEqual('image/gif'); + + const bmpFormat = await getMimeTypeFromBase64( + 'data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABgAAAAAAAQAAADEDgAAxA4AAAAAAAAAAAAAAgD+AA==', + ); + + expect(bmpFormat).toEqual('image/bmp'); + + const webpFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/webp;base64,UklGRkAAAABXRUJQVlA4IDQAAADwAQCdASoBAAEAAQAcJaACdLoB+AAETAAA/vW4f/6aR40jxpHxcP/ugT90CfugT/3NoAAA', + ); + + expect(webpFormat).toEqual('image/webp'); + + const svgFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InJlZCIvPjwvc3ZnPg==', + ); + + expect(svgFormat).toEqual(undefined); + + const svgXmlFormat = await getMimeTypeFromBase64(SVG_XML_TEST); + + expect(svgXmlFormat).toEqual('application/xml'); + + const mp3Format = await getMimeTypeFromBase64(AUDIO_TEST); + + expect(mp3Format).toEqual('audio/mpeg'); + }); + + it('Throws an error when an unrecognized file mimetype is detected.', async () => { + try { + let videoDotLottie = new DotLottie(); + + videoDotLottie = await videoDotLottie.fromArrayBuffer(VIDEO_DOTLOTTIE); + + const videoAnimation = videoDotLottie.getAnimations(); + + if (videoAnimation) { + videoAnimation.map(async (animation) => { + await animation[1].toJSON({ + inlineAssets: true, + }); + }); + } + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/node/animation.spec.ts b/packages/dotlottie-js/src/v1/__tests__/node/animation.spec.ts new file mode 100644 index 00000000..45081b00 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/node/animation.spec.ts @@ -0,0 +1,306 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable no-new */ +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import type { AnimationOptionsV1 as AnimationOptions } from '../../index.node'; +import { LottieAnimationV1 } from '../../index.node'; + +test('throws an error if it receives an invalid id when constructed', () => { + expect(() => { + new LottieAnimationV1({ id: '' } as unknown as AnimationOptions); + }).toThrow('Invalid animation id'); +}); + +test('throws an error if it receives an invalid url when constructed', () => { + const invalidUrl = 'xyz'; + + expect(() => { + new LottieAnimationV1({ id: 'test', url: invalidUrl }); + }).toThrow('Invalid animation url'); +}); + +test('throws an error if it receives an invalid lottie data when constructed', () => { + const invalidData = {} as AnimationType; + + expect(() => { + new LottieAnimationV1({ id: 'test', data: invalidData }); + }).toThrow('Received invalid Lottie data.'); +}); + +test('throws an error if it receives an no data or url when constructed', () => { + expect(() => { + new LottieAnimationV1({ id: 'test' } as unknown as AnimationOptions); + }).toThrow('No data or url provided.'); +}); + +test('gets and sets the zipOptions', () => { + const animation = new LottieAnimationV1({ + id: 'test', + data: animationData as unknown as AnimationType, + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(animation.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + animation.zipOptions = { + level: 1, + }; + + expect(animation.zipOptions).toEqual({ + level: 1, + }); +}); + +test('gets and sets the id', () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.id).toEqual('test'); + + animation.id = 'test2'; + + expect(animation.id).toEqual('test2'); +}); + +test('gets and sets the data', () => { + const animation = new LottieAnimationV1({ id: 'test', url: 'https://example.com' }); + + expect(animation.data).toBeUndefined(); + + animation.data = animationData as unknown as AnimationType; + + expect(animation.data).toEqual(animationData as unknown as AnimationType); +}); + +test('gets and sets the url', () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.url).toBeUndefined(); + + animation.url = 'https://example.com'; + + expect(animation.url).toEqual('https://example.com'); +}); + +describe('toJSON', () => { + test('returns the animation data as a JSON object', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const jsonData = await animation.toJSON(); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + }); + + test('returns the animation with inlined data as a JSON object', async () => { + const animation = new LottieAnimationV1({ + id: 'test', + data: structuredClone(BULL_DATA) as unknown as unknown as AnimationType, + }); + + const jsonData = await animation.toJSON({ inlineAssets: true }); + + expect(jsonData).toEqual(BULL_DATA as unknown as unknown as AnimationType); + }); + + test('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const jsonData = await animation.toJSON(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toJSON()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBase64', () => { + test('returns the base64 of the animation', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const dataUrl = await animation.toBase64(); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + }); + + test('resolves the animation data from the provided url and returns the animation data as a data url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const dataUrl = await animation.toBase64(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toBase64()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBlob', () => { + test('returns the animation data as a blob', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as a blob', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', + }); + + await expect(animation.toBlob()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toArrayBuffer', () => { + test('returns the animation data as an array buffer', async () => { + const animation = new LottieAnimationV1({ id: 'test', data: animationData as unknown as AnimationType }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { + const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimationV1({ + id: 'test', + url: animationURL, + }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimationV1({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toArrayBuffer()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/node/audio.spec.ts b/packages/dotlottie-js/src/v1/__tests__/node/audio.spec.ts new file mode 100644 index 00000000..ca8308a4 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/node/audio.spec.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType, Asset } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; + +import AUDIO_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/audio/instruments_1.json'; +import AUDIO_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/audio/instruments_2.json'; +import { isAudioAsset } from '../../../utils'; +import { DotLottieV1, LottieAudioV1 } from '../../index.node'; + +describe('LottieAudioV1', () => { + it('gets and sets the zipOptions', () => { + const theme = new LottieAudioV1({ + id: 'audio_1', + fileName: 'audio.mp3', + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(theme.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + theme.zipOptions = { + level: 1, + }; + + expect(theme.zipOptions).toEqual({ + level: 1, + }); + }); + + it('Adds two instrument animations via data.', async () => { + await new DotLottieV1() + .addAnimation({ + id: 'animation_1', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(AUDIO_ANIMATION_2_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottieV1) => { + const audio = value.getAudio(); + + expect(audio.length).toBe(6); + + const expectedData: string[] = []; + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + for (let i = 0; i < audio.length; i += 1) { + expect(await audio[i]?.toDataURL()).toEqual(expectedData[i]); + } + }); + }); + + it('Adds identical instrument animation twice via data.', async () => { + await new DotLottieV1() + .addAnimation({ + id: 'animation_1', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(AUDIO_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottieV1) => { + const audio = value.getAudio(); + + expect(audio.length).toBe(6); + + const expectedData: string[] = []; + + // eslint-disable-next-line array-callback-return + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { + expectedData.push(asset.p); + } + }); + + for (let i = 0; i < audio.length; i += 1) { + expect(await audio[i]?.toDataURL()).toEqual(expectedData[i % 3]); + } + }); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/node/dotlottie.spec.ts b/packages/dotlottie-js/src/v1/__tests__/node/dotlottie.spec.ts new file mode 100644 index 00000000..52605048 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/node/dotlottie.spec.ts @@ -0,0 +1,847 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { unzipSync } from 'fflate'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import bullData from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import dotlottieAnimation from '../../../__tests__/__fixtures__/simple/animation.lottie?arraybuffer'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import manifest from '../../../__tests__/__fixtures__/simple/animation/manifest.json'; +import bigMergedDotLottie from '../../../__tests__/__fixtures__/simple/big-merged-dotlottie.lottie?arraybuffer'; +import editedDotlottieAnimation from '../../../__tests__/__fixtures__/simple/edited-settings.lottie?arraybuffer'; +import editedAnimationData from '../../../__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json'; +import editedManifest from '../../../__tests__/__fixtures__/simple/edited-settings/manifest.json'; +import type { AnimationData } from '../../../types'; +import type { AnimationOptionsV1 as AnimationOptions, ManifestAnimationV1, ManifestV1 } from '../../index.node'; +import { DotLottieV1 as DotLottie, LottieAnimationV1 as LottieAnimation, PlayMode } from '../../index.node'; + +describe('setAuthor', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setAuthor('Design Barn'); + + expect(result).toBe(dotlottie); + }); + + test('sets the author', () => { + const dotlottie = new DotLottie(); + + dotlottie.setAuthor('Design Barn'); + + expect(dotlottie.author).toBe('Design Barn'); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setAuthor(''); + + expect(dotlottie.author).toBe(''); + }); +}); + +describe('setRevision', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setRevision(1); + + expect(result).toBe(dotlottie); + }); + + test('sets the revision', () => { + const dotlottie = new DotLottie(); + + const revision = 1.5; + + dotlottie.setRevision(revision); + + expect(dotlottie.revision).toBe(revision); + }); +}); + +describe('setDescription', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setDescription('A description'); + + expect(result).toBe(dotlottie); + }); + + test('sets the description', () => { + const dotlottie = new DotLottie(); + + dotlottie.setDescription('A description'); + + expect(dotlottie.description).toBe('A description'); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setDescription(''); + + expect(dotlottie.description).toBe(''); + }); +}); + +describe('setRevision', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setRevision(1); + + expect(result).toBe(dotlottie); + }); + + test('sets the revision', () => { + const dotlottie = new DotLottie(); + + const revision = 1.5; + + dotlottie.setRevision(revision); + + expect(dotlottie.revision).toBe(revision); + }); +}); + +describe('setKeywords', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.setKeywords('animation, design, lottie'); + + expect(result).toBe(dotlottie); + }); + + test('sets the keywords', () => { + const dotlottie = new DotLottie(); + + const keywords = 'animation, design, lottie'; + + dotlottie.setKeywords(keywords); + + expect(dotlottie.keywords).toBe(keywords); + }); + + test('accepts empty string', () => { + const dotlottie = new DotLottie(); + + dotlottie.setKeywords(''); + + expect(dotlottie.keywords).toBe(''); + }); +}); + +describe('addAnimation', () => { + test('throws an error if it receives a duplicate id when constructed', () => { + expect(() => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + }).toThrow('Duplicate animation id detected, aborting.'); + }); + + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('adds an animation', () => { + const animationId = manifest.animations[0]?.id as string; + + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: animationId, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + const animation = dotlottie.animations[0]; + + expect(animation?.id).toBe(manifest.animations[0]?.id); + }); + + test('adds an animation using all customizable options', async () => { + const animationId = 'test_animation'; + + const dotlottie = new DotLottie(); + + const animationOptions: AnimationOptions = { + id: animationId, + data: animationData as unknown as AnimationType, + autoplay: true, + direction: -1, + hover: true, + intermission: 1000, + loop: true, + playMode: PlayMode.Bounce, + speed: 1.5, + }; + + const manifestDataToCompare: ManifestAnimationV1 = { + id: animationId, + autoplay: true, + direction: -1, + hover: true, + intermission: 1000, + loop: true, + playMode: PlayMode.Bounce, + speed: 1.5, + }; + + dotlottie.addAnimation({ + ...animationOptions, + }); + + await dotlottie.build(); + + const animationManifest = dotlottie.manifest.animations[0]; + + expect(animationManifest).toEqual(manifestDataToCompare); + }); +}); + +describe('removeAnimation', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('removes an animation', () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + dotlottie.removeAnimation(manifest.animations[0]?.id as string); + + expect(dotlottie.animations.length).toBe(0); + }); +}); + +describe('getAnimation', () => { + test('returns animation instance', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); + + expect(animation).toBeInstanceOf(LottieAnimation); + + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(animationData as unknown as AnimationType); + }); + + test('returns undefined if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + const animation = await dotlottie.getAnimation('non_existent_animation'); + + expect(animation).toBeUndefined(); + }); + + test('returns animation instance with inlined assets', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: structuredClone(bullData) as unknown as AnimationData, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); + + expect(animation).toBeInstanceOf(LottieAnimation); + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(bullData as unknown as AnimationData); + }); + + test('adds multiple animations and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); + + dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, + }); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); + + test('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); + + await dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }) + .build(); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); +}); + +describe('download', () => { + test('throws error on node environment', async () => { + // skip test if running in browser environment + if (typeof window !== 'undefined') return; + + const dotlottie = new DotLottie(); + + await expect( + dotlottie + .addAnimation({ + id: 'test_animation', + data: animationData as unknown as AnimationType, + }) + .download('file'), + ).rejects.toThrow('Cannot download dotlottie in a non-browser environment'); + }); + + test('downloads dotlottie file on browser', async () => { + // skip test if running in node environment + if (typeof window === 'undefined') return; + + const dotlottie = new DotLottie(); + + const fileName = 'test.lottie'; + + const fakeLink = document.createElement('a'); + + const clickSpy = vi.spyOn(fakeLink, 'click').mockImplementation(() => { + // do nothing + }); + + vi.spyOn(document, 'createElement').mockImplementation(() => { + return fakeLink; + }); + + const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL').mockImplementation(() => { + return 'blob:url'; + }); + + await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }) + .build(); + + await dotlottie.download(fileName); + + const blob = await dotlottie.toBlob(); + + expect(createObjectURLSpy).toHaveBeenCalledTimes(1); + expect(createObjectURLSpy).toHaveBeenCalledWith(blob); + + expect(fakeLink.download).toBe(fileName); + expect(fakeLink.style.display).toBe('none'); + expect(clickSpy).toHaveBeenCalledTimes(1); + }); +}); + +describe('toBlob', () => { + test('returns a blob', async () => { + const dotlottie = new DotLottie(); + + const blob = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + // const arrayBuffer = await blob.arrayBuffer(); + + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotlottie = new DotLottie(); + + const blob1 = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob({ + zipOptions: { + level: 9, + }, + }); + + const blob2 = await dotlottie.toBlob({ + zipOptions: { + level: 0, + }, + }); + + expect(blob1).toBeInstanceOf(Blob); + expect(blob2).toBeInstanceOf(Blob); + + const arrayBuffer1 = await blob1.arrayBuffer(); + const arrayBuffer2 = await blob2.arrayBuffer(); + + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toArrayBuffer', () => { + test('returns an array buffer', async () => { + const dotlottie = new DotLottie(); + + const arrayBuffer = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const arrayBuffer1 = await dotLottie1 + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer({ + zipOptions: { + level: 9, + }, + }); + + const arrayBuffer2 = await dotLottie1.toArrayBuffer({ + zipOptions: { + level: 0, + }, + }); + + expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toBase64', () => { + test('returns base64 string', async () => { + const dotlottie = new DotLottie(); + + const dataURL = await dotlottie + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64(); + + const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; + + expect(actualArrayBuffer).toBeInstanceOf(ArrayBuffer); + + const actualContent = unzipSync(new Uint8Array(actualArrayBuffer)); + + expect(Object.keys(actualContent).length).toBeGreaterThan(0); + expect(actualContent[`animations/${manifest.animations[0]?.id}.json`]).toBeDefined(); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const dataURL1 = await dotLottie1 + .setAuthor(manifest.author) + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64({ + zipOptions: { + level: 9, + }, + }); + + const dataURL2 = await dotLottie1.toBase64({ + zipOptions: { + level: 0, + }, + }); + + const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; + const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; + + // expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); + }); +}); + +describe('fromURL', () => { + test('throws an error if the URL is invalid', async () => { + const dotLottie = new DotLottie(); + + await expect(dotLottie.fromURL('invalid-url')).rejects.toThrow('Invalid URL'); + }); + + test('loads a dotlottie file from a URL', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(dotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + const dotLottie = await new DotLottie().fromURL(animationURL); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotLottie.animations.length).toBe(1); + expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotLottie.manifest).toEqual(manifest as ManifestV1); + + fetchSpy.mockRestore(); + }); + + test('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(editedDotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual(editedManifest as ManifestV1); + + fetchSpy.mockRestore(); + }); +}); + +describe('fromArrayBuffer', () => { + test('loads a dotlottie file from an array buffer', async () => { + const arrayBuffer = dotlottieAnimation; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual(manifest as ManifestV1); + }); + + test('loads a dotLottie containing images from an array buffer', async () => { + const arrayBuffer = bigMergedDotLottie; + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(6); + expect(dotlottie.getImages().length).toBe(16); + expect(dotlottie.animations[0]?.id).toEqual('v1'); + expect(dotlottie.animations[1]?.id).toEqual('v2'); + expect(dotlottie.animations[2]?.id).toEqual('v3'); + expect(dotlottie.animations[3]?.id).toEqual('v4'); + expect(dotlottie.animations[4]?.id).toEqual('v5'); + expect(dotlottie.animations[5]?.id).toEqual('v6'); + expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); + }); +}); + +describe('imageAssets', () => { + test('Adds the Bull animation and checks number of images.', async () => { + const dotlottie = await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(bullData) as unknown as AnimationData, + }) + .build(); + + const animation1 = await dotlottie.getAnimation('animation_1'); + + expect(animation1?.imageAssets.length).toBe(5); + }); +}); + +describe('merge', () => { + test('merges two dotlottie files', async () => { + const dotlottie1 = new DotLottie().addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }); + + const dotlottie2 = new DotLottie().addAnimation({ + id: 'lottie2', + data: animationData as unknown as AnimationType, + }); + + const dotlottie3 = new DotLottie().addAnimation({ + id: 'lottie3', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const dotlottie4 = new DotLottie().addAnimation({ + id: 'lottie4', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const shrekVariant1 = new DotLottie().addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }); + + const shrekVariant2 = new DotLottie().addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }); + + const shrekVariant3 = new DotLottie().addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }); + + const shrekVariant4 = new DotLottie().addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }); + + const shrekVariant5 = new DotLottie().addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }); + + const shrekVariant6 = new DotLottie().addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }); + + const [mergedImageLottie] = await Promise.all([ + new DotLottie().merge(dotlottie3, dotlottie4), + new DotLottie().merge(dotlottie3, dotlottie4).build(), + ]); + + const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ + new DotLottie() + .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) + .build(), + new DotLottie().merge(dotlottie1, dotlottie2).build(), + ]); + + expect(mergedImageLottie.animations.length).toBe(2); + + expect(bigMergedImageLottie.animations.length).toBe(6); + + expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ + 'v1', + 'v2', + 'v3', + 'v4', + 'v5', + 'v6', + ]); + + expect(mergedDotlottie).toBeInstanceOf(DotLottie); + + expect(mergedDotlottie.animations.length).toBe(2); + + expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); + expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); + expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); + }); +}); + +describe('build', () => { + test('it resolves lottie animations', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation.json'; + + const dotlottie = new DotLottie().addAnimation({ + url: animationURL, + id: 'lottie1', + }); + + expect(dotlottie.animations[0]?.data).toBeUndefined(); + + await dotlottie.build(); + + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/v1/__tests__/node/image.spec.ts b/packages/dotlottie-js/src/v1/__tests__/node/image.spec.ts new file mode 100644 index 00000000..4e1058e2 --- /dev/null +++ b/packages/dotlottie-js/src/v1/__tests__/node/image.spec.ts @@ -0,0 +1,321 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import DUPES_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import AUDIO_TEST from '../../../__tests__/__fixtures__/mimetype-tests/mp-3-test.txt?raw'; +import SVG_XML_TEST from '../../../__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt?raw'; +import VIDEO_DOTLOTTIE from '../../../__tests__/__fixtures__/simple/video-embedded.lottie?arraybuffer'; +import { getMimeTypeFromBase64 } from '../../../utils'; +import { DotLottieV1 as DotLottie, LottieImageV1 as LottieImage } from '../../index.node'; + +describe('LottieImage', () => { + it('gets and sets the zipOptions', () => { + const theme = new LottieImage({ + id: 'image_1', + lottieAssetId: 'image_1', + fileName: 'image_1.png', + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(theme.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + theme.zipOptions = { + level: 1, + }; + + expect(theme.zipOptions).toEqual({ + level: 1, + }); + }); + + it('Adds two bull animations via data.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.png', + 'image_1.png', + 'image_2.png', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + }); + }); + + it('Disables image duplicate detection.', async () => { + await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + expect(value.getImages().length).toBe(10); + }); + }); + + it('Adds two bull animations, one via data the other via URL.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(BULL_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.png', + 'image_1.png', + 'image_2.png', + 'image_3.png', + 'image_4.png', + ]); + }); + }); + + it('Adds an animation with lots of duplicate images.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(DUPES_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(4); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); + }); + }); + + it('Adds an animation with lots of duplicate images but disables image duplicate detection.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: false }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(DUPES_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_2.jpeg', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); + }); + }); + + it('Adds multiple variants of an animation with duplicate images.', async () => { + await new DotLottie({ enableDuplicateImageOptimization: true }) + .addAnimation({ + id: 'animation_0', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationType, + }) + .addAnimation({ + id: 'animation_5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationType, + }) + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); + + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); + + expect(uniqueImages.length).toBe(5); + + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_3.png', + 'image_4.png', + 'image_1_1.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_3', + 'image_4', + 'image_1_1', + ]); + }); + }); + + it('getMimeTypeFromBase64 Properly detects mimetype of images.', async () => { + const jpegFormat = await getMimeTypeFromBase64( + 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAZABkAAD/2wCEABQQEBkSGScXFycyJh8mMi4mJiYmLj41NTU1NT5EQUFBQUFBREREREREREREREREREREREREREREREREREREREQBFRkZIBwgJhgYJjYmICY2RDYrKzZERERCNUJERERERERERERERERERERERERERERERERERERERERERERERERERP/AABEIAAEAAQMBIgACEQEDEQH/xABMAAEBAAAAAAAAAAAAAAAAAAAABQEBAQAAAAAAAAAAAAAAAAAABQYQAQAAAAAAAAAAAAAAAAAAAAARAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhEDEQA/AJQA9Yv/2Q==', + ); + + expect(jpegFormat).toEqual('image/jpeg'); + + const pngFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC', + ); + + expect(pngFormat).toEqual('image/png'); + + const gifFormat = await getMimeTypeFromBase64( + 'data:image/gif;base64,R0lGODdhAQABAPAAAP8AAAAAACwAAAAAAQABAAACAkQBADs=', + ); + + expect(gifFormat).toEqual('image/gif'); + + const bmpFormat = await getMimeTypeFromBase64( + 'data:image/bmp;base64,Qk06AAAAAAAAADYAAAAoAAAAAQAAAAEAAAABABgAAAAAAAQAAADEDgAAxA4AAAAAAAAAAAAAAgD+AA==', + ); + + expect(bmpFormat).toEqual('image/bmp'); + + const webpFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/webp;base64,UklGRkAAAABXRUJQVlA4IDQAAADwAQCdASoBAAEAAQAcJaACdLoB+AAETAAA/vW4f/6aR40jxpHxcP/ugT90CfugT/3NoAAA', + ); + + expect(webpFormat).toEqual('image/webp'); + + const svgFormat = await getMimeTypeFromBase64( + // eslint-disable-next-line no-secrets/no-secrets + 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMSIgaGVpZ2h0PSIxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPjxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9InJlZCIvPjwvc3ZnPg==', + ); + + expect(svgFormat).toEqual(undefined); + + const svgXmlFormat = await getMimeTypeFromBase64(SVG_XML_TEST); + + expect(svgXmlFormat).toEqual('application/xml'); + + const mp3Format = await getMimeTypeFromBase64(AUDIO_TEST); + + expect(mp3Format).toEqual('audio/mpeg'); + }); + + it('Throws an error when an unrecognized file mimetype is detected.', async () => { + try { + let videoDotLottie = new DotLottie(); + + videoDotLottie = await videoDotLottie.fromArrayBuffer(VIDEO_DOTLOTTIE); + + const videoAnimation = videoDotLottie.getAnimations(); + + if (videoAnimation) { + videoAnimation.map(async (animation) => { + await animation[1].toJSON({ + inlineAssets: true, + }); + }); + } + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); +}); diff --git a/packages/dotlottie-js/src/v1/browser/animation.ts b/packages/dotlottie-js/src/v1/browser/animation.ts new file mode 100644 index 00000000..7c03f6c6 --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/animation.ts @@ -0,0 +1,135 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; + +import { DotLottieError, getExtensionTypeFromBase64, isAudioAsset } from '../../utils'; +import type { AnimationOptionsV1 } from '../common'; +import { LottieAnimationCommonV1 } from '../common'; + +import { LottieAudioV1 } from './audio'; +import { LottieImageV1 } from './image'; + +export class LottieAnimationV1 extends LottieAnimationCommonV1 { + public constructor(options: AnimationOptionsV1) { + super(options); + } + + /** + * Return the animation data as a base64 encoded string. + * + * @returns data - The animation data as a base64 encoded string. + * @throws Error - if the animation data is not set and the url is not provided. + * @throws Error - if the animation data is not a valid Lottie animation data object. + * @throws Error - if the fetch request fails. + */ + public override async toBase64(): Promise { + const data = await this.toArrayBuffer(); + + if (typeof window === 'undefined') return Buffer.from(data).toString('base64'); + + const uint8Array = new Uint8Array(data); + const binaryString = uint8Array.reduce((acc, val) => acc + String.fromCharCode(val), ''); + + return window.btoa(binaryString); + } + + /** + * + * Extract image assets from the animation. + * + * @returns boolean - true on error otherwise false on success + */ + protected override async _extractImageAssets(): Promise { + if (!this._data) throw new DotLottieError('Failed to extract image assets: Animation data does not exist'); + + const animationAssets = this._data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); + + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset && !('xt' in asset) && 'p' in asset) { + const imageData = asset.p.split(','); + + // Image data is invalid + if (!imageData.length || !imageData[0] || !imageData[1]) { + break; + } + + let extType = null; + const fileType = await getExtensionTypeFromBase64(asset.p); + + // If we don't recognize the file type, we leave it inside the animation as is. + if (fileType) { + extType = fileType; + + const fileName = `${asset.id}.${extType}`; + + this._imageAssets.push( + new LottieImageV1({ + data: asset.p, + id: asset.id, + lottieAssetId: asset.id, + fileName, + parentAnimations: [this], + }), + ); + + asset.p = fileName; + asset.u = '/images/'; + asset.e = 0; + } + } + } + + return false; + } + + /** + * + * Extract audio assets from the animation. + * + * @returns boolean - true on error otherwise false on success + */ + protected override async _extractAudioAssets(): Promise { + if (!this._data) throw new DotLottieError('Failed to extract audio assets: Animation data does not exist'); + + const animationAssets = this._data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); + + for (const asset of animationAssets) { + if (isAudioAsset(asset)) { + const audioData = asset.p.split(','); + + // Audio data is invalid + if (!audioData.length || !audioData[0] || !audioData[1]) { + break; + } + + let extType = null; + const fileType = await getExtensionTypeFromBase64(asset.p); + + extType = fileType; + + const fileName = `${asset.id}.${extType}`; + + this._audioAssets.push( + new LottieAudioV1({ + data: asset.p, + id: asset.id, + fileName, + parentAnimations: [this], + }), + ); + + asset.p = fileName; + asset.u = '/audio/'; + asset.e = 0; + } + } + + return false; + } +} diff --git a/packages/dotlottie-js/src/v1/browser/audio.ts b/packages/dotlottie-js/src/v1/browser/audio.ts new file mode 100644 index 00000000..b2617af4 --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/audio.ts @@ -0,0 +1,12 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { AudioOptionsV1 } from '../common'; +import { LottieAudioCommonV1 } from '../common'; + +export class LottieAudioV1 extends LottieAudioCommonV1 { + public constructor(options: AudioOptionsV1) { + super(options); + } +} diff --git a/packages/dotlottie-js/src/v1/browser/dotlottie.ts b/packages/dotlottie-js/src/v1/browser/dotlottie.ts new file mode 100644 index 00000000..d58ff477 --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/dotlottie.ts @@ -0,0 +1,343 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @typescript-eslint/no-use-before-define */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import type { Zippable } from 'fflate'; +import { strToU8, zip, strFromU8, unzip } from 'fflate'; + +import type { ConversionOptions } from '../../types'; +import { + base64ToUint8Array, + DotLottieError, + getDotLottieVersion, + getExtensionTypeFromBase64, + isAudioAsset, +} from '../../utils'; +import { DotLottie } from '../../v2/browser'; +import type { AnimationOptionsV1, DotLottieV1Options } from '../common'; +import { DotLottieCommonV1 } from '../common'; +import type { ManifestV1 } from '../common/schemas/manifest'; + +import { LottieAnimationV1 } from './animation'; +import { LottieAudioV1 } from './audio'; +import { LottieImageV1 } from './image'; +import { DuplicateImageDetector } from './plugins/duplicate-image-detector'; + +export async function toDotLottieV1(arrayBuffer: ArrayBuffer): Promise { + const version = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (version === '2') { + const dotLottieV1 = new DotLottieV1(); + + const dotLottieV2 = await new DotLottie().fromArrayBuffer(arrayBuffer); + + const animationIds = dotLottieV2.animations.map((animation) => animation.id); + + for (const animationId of animationIds) { + const animation = await dotLottieV2.getAnimation(animationId, { inlineAssets: true }); + + if (animation && animation.data) { + dotLottieV1.addAnimation({ + data: animation.data, + id: animationId, + }); + } + } + + await dotLottieV1.build(); + + return dotLottieV1; + } else { + return new DotLottieV1().fromArrayBuffer(arrayBuffer); + } +} + +export class DotLottieV1 extends DotLottieCommonV1 { + public constructor(options?: DotLottieV1Options) { + super(options); + + if (this.enableDuplicateImageOptimization) { + const plugin = new DuplicateImageDetector(); + + plugin.install(this); + + this._plugins.push(plugin); + } + } + + public override addAnimation(animationOptions: AnimationOptionsV1): DotLottieV1 { + const animation = new LottieAnimationV1(animationOptions); + + if (this._animationsMap.get(animationOptions.id)) { + throw new DotLottieError('Duplicate animation id detected, aborting.'); + } + + this._animationsMap.set(animation.id, animation); + + return this; + } + + public override async toBase64(options?: ConversionOptions): Promise { + const data = await this.toArrayBuffer(options); + + const uint8Array = new Uint8Array(data); + const binaryString = uint8Array.reduce((acc, val) => acc + String.fromCharCode(val), ''); + + return window.btoa(binaryString); + } + + public override async download(fileName: string, options?: ConversionOptions): Promise { + const blob = await this.toBlob(options); + + const dataURL = URL.createObjectURL(blob); + + const link = document.createElement('a'); + + link.href = dataURL; + + link.download = fileName; + + link.style.display = 'none'; + + document.body.append(link); + + link.click(); + + setTimeout(() => { + URL.revokeObjectURL(dataURL); + link.remove(); + }, 1000); + } + + public override create(options?: DotLottieV1Options): DotLottieCommonV1 { + return new DotLottieV1(options); + } + + public override async toArrayBuffer(options?: ConversionOptions): Promise { + const manifest = this._buildManifest(); + + const dotLottie: Zippable = { + 'manifest.json': [strToU8(JSON.stringify(manifest)), {}], + }; + + for (const animation of this.animations) { + const json = await animation.toJSON(); + + dotLottie[`animations/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; + + const imageAssets = animation.imageAssets; + const audioAssets = animation.audioAssets; + + for (const asset of imageAssets) { + // Assure we have a base64 encoded version of the image + const dataAsString = await asset.toDataURL(); + + dotLottie[`images/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + } + + for (const asset of audioAssets) { + // Assure we have a base64 encoded version of the audio + const dataAsString = await asset.toDataURL(); + + dotLottie[`audio/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + } + } + + const dotLottieArrayBuffer = await new Promise((resolve, reject) => { + zip(dotLottie, options?.zipOptions || {}, (err, data) => { + if (err) { + reject(err); + + return; + } + + resolve(data.buffer); + }); + }); + + return dotLottieArrayBuffer; + } + + /** + * Creates a DotLottieV1 instance from an array buffer + * @param arrayBuffer - array buffer of the DotLottieV1 file + * @returns DotLottieV1 instance + * @throws Error + */ + public override async fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise { + const dotLottieVersion = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (dotLottieVersion === '2') { + return toDotLottieV1(arrayBuffer); + } + + const dotLottie = new DotLottieV1(); + + try { + const contentObj = await new Promise((resolve, reject) => { + unzip(new Uint8Array(arrayBuffer), (err, data) => { + if (err) { + reject(err); + } + + resolve(data); + }); + }); + + const tmpImages = []; + const tmpAudio = []; + + if (contentObj['manifest.json'] instanceof Uint8Array) { + // valid buffer + try { + // Parse the manifest first so that we can pick up animation settings + const manifest = JSON.parse(strFromU8(contentObj['manifest.json'], false)) as ManifestV1; + const { author, custom, description, keywords } = manifest; + + if (author) { + this._requireValidAuthor(author); + dotLottie.setAuthor(author); + } + if (custom) { + this._requireValidCustomData(custom); + dotLottie.setCustomData(custom); + } + if (description) { + this._requireValidDescription(description); + dotLottie.setDescription(description); + } + if (keywords) { + this._requireValidKeywords(keywords); + dotLottie.setKeywords(keywords); + } + + for (const key of Object.keys(contentObj)) { + // true is passed to use binary string, otherwise btoa fails + const decodedStr = strFromU8(contentObj[key] as Uint8Array, true); + + if (key.startsWith('animations/') && key.endsWith('.json')) { + // extract animationId from key as the key = `animations/${animationId}.json` + const animationId = /animations\/(.+)\.json/u.exec(key)?.[1]; + + if (!animationId) { + throw new DotLottieError('Invalid animation id'); + } + + const animation = JSON.parse(decodedStr); + + const animationSettings = manifest.animations.find((anim) => anim.id === animationId); + + if (animationSettings === undefined) { + throw new DotLottieError('Animation not found inside manifest'); + } + + dotLottie.addAnimation({ + data: animation, + ...animationSettings, + }); + } else if (key.startsWith('images/')) { + // extract imageId from key as the key = `images/${imageId}.${ext}` + const imageId = /images\/(.+)\./u.exec(key)?.[1]; + + if (!imageId) { + throw new DotLottieError('Invalid image id'); + } + + let decodedImg = btoa(decodedStr); + + const ext = await getExtensionTypeFromBase64(decodedImg); + + // Push the images in to a temporary array + decodedImg = `data:image/${ext};base64,${decodedImg}`; + tmpImages.push( + new LottieImageV1({ + id: imageId, + lottieAssetId: imageId, + data: decodedImg, + fileName: key.split('/')[1] || '', + }), + ); + } else if (key.startsWith('audio/')) { + // extract audioId from key as the key = `audio/${audioId}.${ext}` + const audioId = /audio\/(.+)\./u.exec(key)?.[1]; + + if (!audioId) { + throw new DotLottieError('Invalid image id'); + } + + let decodedAudio = btoa(decodedStr); + + const ext = await getExtensionTypeFromBase64(decodedAudio); + + // Push the audio in to a temporary array + decodedAudio = `data:audio/${ext};base64,${decodedAudio}`; + tmpAudio.push( + new LottieAudioV1({ + id: audioId, + data: decodedAudio, + fileName: key.split('/')[1] || '', + }), + ); + } + } + + // Go through the images and find to which animation they belong + for (const image of tmpImages) { + for (const parentAnimation of dotLottie.animations) { + if (parentAnimation.data) { + const animationAssets = parentAnimation.data.assets as AnimationType['assets']; + + if (animationAssets) { + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset) { + if (asset.p === image.fileName) { + image.parentAnimations.push(parentAnimation); + parentAnimation.imageAssets.push(image); + } + } + } + } + } + } + } + + // Go through the audio and find to which animation they belong + for (const audio of tmpAudio) { + for (const parentAnimation of dotLottie.animations) { + if (parentAnimation.data) { + const animationAssets = parentAnimation.data.assets as AnimationType['assets']; + + if (animationAssets) { + for (const asset of animationAssets) { + if (isAudioAsset(asset)) { + if (asset.p.includes(audio.id)) { + audio.parentAnimations.push(parentAnimation); + parentAnimation.audioAssets.push(audio); + } + } + } + } + } + } + } + } catch (err) { + if (err instanceof Error) { + throw new DotLottieError(`Invalid manifest inside buffer! ${err.message}`); + } + } + } else { + // throw error as it's invalid buffer + throw new DotLottieError('Invalid buffer'); + } + } catch (err) { + if (err instanceof Error) { + throw new DotLottieError(err.message); + } + } + + return dotLottie; + } +} diff --git a/packages/dotlottie-js/src/v1/browser/image.ts b/packages/dotlottie-js/src/v1/browser/image.ts new file mode 100644 index 00000000..a2ade2ee --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/image.ts @@ -0,0 +1,12 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ImageOptionsV1 } from '../common'; +import { LottieImageCommonV1 } from '../common'; + +export class LottieImageV1 extends LottieImageCommonV1 { + public constructor(options: ImageOptionsV1) { + super(options); + } +} diff --git a/packages/dotlottie-js/src/v1/browser/index.ts b/packages/dotlottie-js/src/v1/browser/index.ts new file mode 100644 index 00000000..b237f98b --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/index.ts @@ -0,0 +1,8 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './audio'; diff --git a/packages/dotlottie-js/src/v1/browser/plugins/duplicate-image-detector.ts b/packages/dotlottie-js/src/v1/browser/plugins/duplicate-image-detector.ts new file mode 100644 index 00000000..bb634ab1 --- /dev/null +++ b/packages/dotlottie-js/src/v1/browser/plugins/duplicate-image-detector.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import { Hash, DifferenceHashBuilder } from 'browser-image-hash'; + +import type { LottieImageCommonV1 } from '../../common'; +import { DuplicateImageDetectorCommon } from '../../common/plugins/duplicate-image-detector'; + +export class DuplicateImageDetector extends DuplicateImageDetectorCommon { + public override async generatePhash(image: LottieImageCommonV1): Promise { + const builder = new DifferenceHashBuilder(); + const targetURL = new URL(await image.toDataURL()); + + const destHash = await builder.build(targetURL); + + return destHash.rawHash; + } + + public override distanceTo(imageHash: string, targetImageHash: string): number { + const srcHash = new Hash(imageHash); + const targetHash = new Hash(targetImageHash); + + return srcHash.getHammingDistance(targetHash); + } +} diff --git a/packages/dotlottie-js/src/v1/common/animation.ts b/packages/dotlottie-js/src/v1/common/animation.ts new file mode 100644 index 00000000..924a890b --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/animation.ts @@ -0,0 +1,540 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ZipOptions } from 'fflate'; + +import type { AnimationData, ExportOptions } from '../../types'; +import { DotLottieError, isAudioAsset } from '../../utils'; + +import type { LottieAudioCommonV1 } from './audio'; +import type { LottieImageCommonV1 } from './image'; +import { PlayMode } from './schemas/manifest'; +import type { ManifestAnimationV1 } from './schemas/manifest'; + +interface AnimationOptionsBase extends ManifestAnimationV1 { + defaultActiveAnimation?: boolean; + zipOptions?: ZipOptions; +} + +interface AnimationOptionsWithData extends AnimationOptionsBase { + data: AnimationData; + url?: never; +} + +interface AnimationOptionsWithUrl extends AnimationOptionsBase { + data?: never; + url: string; +} + +export type AnimationOptionsV1 = AnimationOptionsWithData | AnimationOptionsWithUrl; + +export class LottieAnimationCommonV1 { + protected _data?: AnimationData; + + protected _id: string = ''; + + protected _url?: string; + + private _direction: ManifestAnimationV1['direction']; + + private _speed: number | undefined; + + private _playMode: PlayMode | undefined; + + private _loop: boolean | number | undefined; + + private _autoplay: boolean | undefined; + + private _hover: boolean | undefined; + + private _intermission: number | undefined; + + private _themeColor: string | undefined; + + private _zipOptions: ZipOptions; + + // Will be translated to 'activeAnimationId' inside of the manifest file + // This indicates if the player should play this animation by default rather than the first in the list. + protected _defaultActiveAnimation: boolean; + + protected _imageAssets: LottieImageCommonV1[] = []; + + protected _audioAssets: LottieAudioCommonV1[] = []; + + public constructor(options: AnimationOptionsV1) { + this._requireValidOptions(options); + + this._id = options.id; + + this._zipOptions = options.zipOptions ?? {}; + + if (options.data) this._data = options.data; + if (options.url) this._url = options.url; + + this._defaultActiveAnimation = options.defaultActiveAnimation ?? false; + + if (typeof options.direction === 'number') { + this.direction = options.direction; + } + + if (typeof options.speed === 'number') { + this.speed = options.speed; + } + + if (typeof options.playMode === 'string') { + this.playMode = options.playMode; + } + + if (typeof options.loop === 'boolean' || typeof options.loop === 'number') { + this.loop = options.loop; + } + + if (typeof options.autoplay === 'boolean') { + this.autoplay = options.autoplay; + } + + if (typeof options.hover === 'boolean') { + this.hover = options.hover; + } + + if (typeof options.intermission === 'number') { + this.intermission = options.intermission; + } + + if (typeof options.themeColor === 'string') { + this.themeColor = options.themeColor; + } + } + + public async toBase64(): Promise { + throw new DotLottieError('lottie animation controls tobase64 not implemented!'); + } + + public get zipOptions(): ZipOptions { + return this._zipOptions; + } + + public set zipOptions(zipOptions: ZipOptions) { + this._zipOptions = zipOptions; + } + + public get id(): string { + return this._id; + } + + public set id(id: string) { + this._requireValidId(id); + + this._id = id; + } + + public get imageAssets(): LottieImageCommonV1[] { + return this._imageAssets; + } + + public set imageAssets(imageAssets: LottieImageCommonV1[]) { + this._imageAssets = imageAssets; + } + + public get audioAssets(): LottieAudioCommonV1[] { + return this._audioAssets; + } + + public set audioAssets(audioAssets: LottieAudioCommonV1[]) { + this._audioAssets = audioAssets; + } + + public get data(): AnimationData | undefined { + return this._data; + } + + public set data(data: AnimationData | undefined) { + this._requireValidLottieData(data); + + this._data = data; + } + + public get url(): string | undefined { + return this._url; + } + + public set url(url: string | undefined) { + this._requireValidUrl(url); + + this._url = url; + } + + public get themeColor(): string | undefined { + return this._themeColor; + } + + public set themeColor(themeColor: string | undefined) { + if (themeColor) { + this._requireValidThemeColor(themeColor); + } + + this._themeColor = themeColor; + } + + public get direction(): ManifestAnimationV1['direction'] { + return this._direction; + } + + public set direction(direction: ManifestAnimationV1['direction']) { + this._direction = direction; + } + + public get speed(): number | undefined { + return this._speed; + } + + public set speed(speed: number | undefined) { + if (typeof speed === 'number') { + this._requireValidSpeed(speed); + } + + this._speed = speed; + } + + public get playMode(): PlayMode | undefined { + return this._playMode; + } + + public set playMode(playMode: PlayMode | undefined) { + if (typeof playMode === 'string') { + this._requireValidPlayMode(playMode); + } + + this._playMode = playMode; + } + + public get loop(): boolean | number | undefined { + return this._loop; + } + + public set loop(loop: boolean | number | undefined) { + if (typeof loop === 'number' || typeof loop === 'boolean') { + this._requireValidLoop(loop); + } + + this._loop = loop; + } + + public get autoplay(): boolean | undefined { + return this._autoplay; + } + + public set autoplay(autoplay: boolean | undefined) { + if (typeof autoplay === 'boolean') { + this._requireValidAutoplay(autoplay); + } + + this._autoplay = autoplay; + } + + public get defaultActiveAnimation(): boolean { + return this._defaultActiveAnimation; + } + + public set defaultActiveAnimation(defaultActiveAnimation: boolean) { + this._defaultActiveAnimation = defaultActiveAnimation; + } + + public get hover(): boolean | undefined { + return this._hover; + } + + public set hover(hover: boolean | undefined) { + if (typeof hover === 'boolean') { + this._requireValidHover(hover); + } + + this._hover = hover; + } + + public get intermission(): number | undefined { + return this._intermission; + } + + public set intermission(intermission: number | undefined) { + if (typeof intermission === 'number') { + this._requireValidIntermission(intermission); + } + + this._intermission = intermission; + } + + /** + * Return the animation data as an array buffer. + * @returns data - The animation data as an ArrayBuffer. + * @throws Error - if the animation data is not set and the url is not provided. + * @throws Error - if the animation data is not a valid Lottie animation data object. + * @throws Error - if the fetch request fails. + */ + public async toArrayBuffer(options?: ExportOptions): Promise { + const dataJson = await this.toJSON(options); + + return new TextEncoder().encode(JSON.stringify(dataJson)).buffer; + } + + protected async _extractImageAssets(): Promise { + throw new DotLottieError('_extractImageAssets(): Promise method not implemented in concrete class'); + } + + protected async _extractAudioAssets(): Promise { + throw new DotLottieError('_extractAudioAssets(): Promise method not implemented in concrete class'); + } + + /** + * Return the animation data as a blob. + * @returns blob - The animation data as a Blob. + * @throws Error - if the animation data is not set and the url is not provided. + * @throws Error - if the animation data is not a valid Lottie animation data object. + * @throws Error - if the fetch request fails. + */ + public async toBlob(options: ExportOptions = {}): Promise { + const dataJson = await this.toJSON(options); + + return new Blob([JSON.stringify(dataJson)], { type: 'application/json' }); + } + + /** + * Return the animation data as a JSON object. + * If the animation data is not already set, it will be fetched from the provided url. + * @returns data - The animation data. + * @throws Error - if the animation data is not a valid Lottie animation data object. + * @throws Error - if the fetch request fails. + */ + public async toJSON(options?: ExportOptions): Promise { + if (this._url && !this._data) { + this._data = await this._fromUrl(this._url); + } + + this._requireValidLottieData(this._data); + + if (this._data.assets?.length) { + // Even if the user wants to inline the assets, we still need to extract them + await this._extractImageAssets(); + await this._extractAudioAssets(); + + if (options?.inlineAssets) { + const animationAssets = this.data?.assets as AnimationData['assets']; + + if (!animationAssets) + throw new DotLottieError("Failed to inline assets, the animation's assets are undefined."); + + const images = this.imageAssets; + const audios = this.audioAssets; + + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset && !('xt' in asset) && 'p' in asset) { + for (const image of images) { + // If we only compare paths, we can leave the asset.id alone + if (image.fileName === asset.p) { + // encoded is true + asset.e = 1; + asset.u = ''; + asset.p = await image.toDataURL(); + } + } + } else if (isAudioAsset(asset)) { + // Audio asset + for (const audio of audios) { + if (audio.fileName === asset.p) { + // encoded is true + asset.e = 1; + asset.u = ''; + asset.p = await audio.toDataURL(); + } + } + } + } + } + } + + return this._data; + } + + /** + * Fetch the animation data from the provided url. + * @param url - The url to fetch the animation data from. + * @returns animationData - The animation data. + * @throws Error - if the fetch request fails. + * @throws Error - if the data object is not a valid Lottie animation data object. + */ + private async _fromUrl(url: string): Promise { + const response = await fetch(url); + + const text = await response.text(); + + let json; + + try { + json = JSON.parse(text); + } catch (error) { + if (error instanceof Error) { + throw new DotLottieError(`${error.message}: Invalid json returned from url`); + } + } + + this._requireValidLottieData(json as AnimationData); + + return json; + } + + /** + * Ensure that the provided url is a valid string. + * The url must be a non-empty string, otherwise an error will be thrown. + * @param url - The url to validate. + * @throws Error - if the url is not a valid string. + * + */ + private _requireValidUrl(url: string | undefined): asserts url is string { + try { + // eslint-disable-next-line no-new + new URL(url || ''); + } catch (_err) { + throw new DotLottieError('Invalid animation url'); + } + } + + /** + * Ensure that the provided data object is a valid Lottie animation data object. + * The data object must contain the following mandatory properties: v, ip, op, layers, fr, w, h. + * If the data object does not contain all mandatory properties, an error will be thrown. + * @param data - The data object to validate. + * @throws Error - if the data object is not a valid Lottie animation data object. + */ + private _requireValidLottieData(data: AnimationData | undefined): asserts data is AnimationData { + const mandatoryLottieProperties = ['v', 'ip', 'op', 'layers', 'fr', 'w', 'h']; + + const hasAllMandatoryProperties = mandatoryLottieProperties.every((field) => + Object.prototype.hasOwnProperty.call(data, field), + ); + + if (!hasAllMandatoryProperties) { + throw new DotLottieError('Received invalid Lottie data.'); + } + } + + /** + * Ensure that the provided id is a valid string. + * The id must be a non-empty string, otherwise an error will be thrown. + * @param id - The id to validate. + * @throws Error - if the id is not a valid string. + */ + private _requireValidId(id: string | undefined): asserts id is string { + if (!id) throw new DotLottieError('Invalid animation id'); + } + + /** + * Ensure that the provided url is a valid string. + * The url must be a non-empty string, otherwise an error will be thrown. + * @param url - The url to validate. + * @throws Error - if the url is not a valid string. + * + */ + private _requireValidDirection(direction: number): asserts direction is number { + if (direction !== -1 && direction !== 1) { + throw new DotLottieError('Direction can only be -1 (backwards) or 1 (forwards)'); + } + } + + /** + * Ensure that the provided intermission is a valid, positive number. + * @param intermission - The intermission to validate. + * @throws Error - if the intermission is not a valid number. + */ + private _requireValidIntermission(intermission: number): asserts intermission is number { + if (intermission < 0 || !Number.isInteger(intermission)) { + throw new DotLottieError('intermission must be a positive number'); + } + } + + /** + * Ensure that the provided loop is a valid, positive number or boolean. + * @param loop - The loop to validate. + * @throws Error - if the loop is not a valid number or boolean. + */ + private _requireValidLoop(loop: number | boolean): asserts loop is number | boolean { + if (typeof loop === 'number' && (!Number.isInteger(loop) || loop < 0)) { + throw new DotLottieError('loop must be a positive number or boolean'); + } + } + + /** + * Ensure that the provided options object is a valid AnimationOptions object. + * The options object must contain the following mandatory properties: id, data or url. + * If the options object does not contain all mandatory properties, an error will be thrown. + * @param options - The options object to validate. + * @throws Error - if the options object is not a valid AnimationOptions object. + * @throws Error - if the id is not a valid string. + * @throws Error - if the data object is not a valid Lottie animation data object. + * @throws Error - if the url is not a valid url string. + * @throws Error - if the data object is not set and the url is not provided. + */ + private _requireValidOptions(options: AnimationOptionsV1): asserts options is AnimationOptionsV1 { + this._requireValidId(options.id); + + if (!options.data && !options.url) { + throw new DotLottieError('No data or url provided.'); + } + + if (options.data) { + this._requireValidLottieData(options.data); + } + + if (options.url) { + this._requireValidUrl(options.url); + } + + if (options.direction) { + this._requireValidDirection(options.direction); + } + + if (options.intermission) { + this._requireValidIntermission(options.intermission); + } + + if (options.loop) { + this._requireValidLoop(options.loop); + } + } + + private _requireValidSpeed(speed: number | undefined): asserts speed is number { + if (speed !== undefined && (typeof speed !== 'number' || speed < 0)) { + throw new DotLottieError('Speed must be a non-negative number'); + } + } + + private _requireValidPlayMode(playMode: PlayMode | undefined): asserts playMode is PlayMode { + const validPlayModes = Object.values(PlayMode); + + if (playMode !== undefined && !validPlayModes.includes(playMode)) { + throw new DotLottieError(`playMode must be one of: ${validPlayModes.join(', ')}`); + } + } + + private _requireValidAutoplay(autoplay: boolean | undefined): asserts autoplay is boolean { + if (autoplay !== undefined && typeof autoplay !== 'boolean') { + throw new DotLottieError('autoplay must be a boolean'); + } + } + + private _requireValidHover(hover: boolean | undefined): asserts hover is boolean { + if (hover !== undefined && typeof hover !== 'boolean') { + throw new DotLottieError('Hover must be a boolean'); + } + } + + private _requireValidThemeColor(themeColor: string | undefined): asserts themeColor is string { + if (themeColor !== undefined && typeof themeColor !== 'string') { + throw new DotLottieError('themeColor must be a string and start with #'); + } + + if (themeColor !== undefined && !themeColor.startsWith('#')) { + throw new DotLottieError('themeColor must be a string and start with #'); + } + } +} diff --git a/packages/dotlottie-js/src/v1/common/audio.ts b/packages/dotlottie-js/src/v1/common/audio.ts new file mode 100644 index 00000000..c3d12056 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/audio.ts @@ -0,0 +1,214 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ZipOptions } from 'fflate'; + +import type { AudioData } from '../../types'; +import { dataUrlFromU8, DotLottieError, ErrorCodes, getExtensionTypeFromBase64 } from '../../utils'; + +import type { LottieAnimationCommonV1 } from './animation'; + +export interface AudioOptionsV1 { + data?: AudioData; + fileName: string; + id: string; + parentAnimations?: LottieAnimationCommonV1[]; + url?: string; + zipOptions?: ZipOptions; +} + +export class LottieAudioCommonV1 { + protected _data?: AudioData; + + protected _id: string = ''; + + protected _url?: string; + + protected _fileName: string = ''; + + protected _parentAnimations: LottieAnimationCommonV1[]; + + protected _zipOptions: ZipOptions; + + public constructor(options: AudioOptionsV1) { + this._requireValidId(options.id); + this._requireValidFileName(options.fileName); + + this._zipOptions = options.zipOptions ?? {}; + + if (options.data) { + this._data = options.data; + } + + if (options.id) { + this._id = options.id; + } + + if (options.url) { + this._url = options.url; + } + + if (options.fileName) { + this._fileName = options.fileName; + } + + this._parentAnimations = options.parentAnimations || []; + } + + public get zipOptions(): ZipOptions { + return this._zipOptions; + } + + public set zipOptions(zipOptions: ZipOptions) { + this._zipOptions = zipOptions; + } + + public get fileName(): string { + return this._fileName; + } + + public set fileName(fileName: string) { + if (!fileName) throw new DotLottieError('Invalid audio file name', ErrorCodes.ASSET_NOT_FOUND); + this._fileName = fileName; + } + + public get id(): string { + return this._id; + } + + public set id(id: string) { + if (!id) throw new DotLottieError('Invalid audio id', ErrorCodes.ASSET_NOT_FOUND); + this._id = id; + } + + public get data(): AudioData | undefined { + return this._data; + } + + public set data(data: AudioData | undefined) { + if (!data) { + throw new DotLottieError('Invalid data'); + } + + this._data = data; + } + + public get parentAnimations(): LottieAnimationCommonV1[] { + return this._parentAnimations; + } + + public set parentAnimations(parentAnimations: LottieAnimationCommonV1[]) { + this._parentAnimations = parentAnimations; + } + + public async toDataURL(): Promise { + if (this._data && this._isDataURL(this._data)) return this.data as string; + + const arrayBuffer = await this.toArrayBuffer(); + + return dataUrlFromU8(new Uint8Array(arrayBuffer)); + } + + /** + * Renames the id and fileName to newName. + * @param newName - A new id and filename for the audio. + */ + public async renameAudio(newName: string): Promise { + this.id = newName; + + const data = await this.toDataURL(); + + const ext = await getExtensionTypeFromBase64(data); + + if (!ext) { + throw new DotLottieError('File extension type could not be detected from asset file.'); + } + + this.fileName = `${newName}.${ext}`; + } + + public async toArrayBuffer(): Promise { + const blob = await (await this.toBlob()).arrayBuffer(); + + return blob; + } + + public async toBlob(): Promise { + if (!this._data && this._url) { + this._data = await this._fromUrlToBlob(this._url); + } + + if (!this._data) { + throw new Error('Invalid data'); + } + + if (this._isDataURL(this._data)) { + const data = this._data as string; + + const [header, base64] = data.split(','); + + // If the data doesnt contain the encoding URL, return it + if ((!header || !base64) && data.length) { + return new Blob([data]); + } + + if (!header || !base64) { + throw new Error('Invalid data'); + } + + // eslint-disable-next-line require-unicode-regexp + const type = header.replace('data:', '').replace(/;base64$/, ''); + + return new Blob([base64], { type }); + } + + if (this._isArrayBuffer(this._data)) { + return new Blob([this._data]); + } + + if (this._isBlob(this._data)) { + return this._data as Blob; + } + + throw new Error('Invalid data'); + } + + protected async _fromUrlToBlob(url: string): Promise { + const response = await fetch(url); + + return response.blob(); + } + + protected _isArrayBuffer(data: AudioData): boolean { + return data instanceof ArrayBuffer; + } + + protected _isDataURL(data: AudioData): boolean { + return typeof data === 'string' && data.startsWith('data:'); + } + + protected _isBlob(data: AudioData): boolean { + return data instanceof Blob; + } + + /** + * Ensure that the provided id is a valid string. + * The id must be a non-empty string, otherwise an error will be thrown. + * @param id - The id to validate. + * @throws Error - if the id is not a valid string. + */ + private _requireValidId(id: string | undefined): asserts id is string { + if (!id) throw new DotLottieError('Invalid audio id'); + } + + /** + * Ensure that the provided fileName is a valid string. + * The fileName must be a non-empty string, otherwise an error will be thrown. + * @param fileName - The fileName to validate. + * @throws Error - if the fileName is not a valid string. + */ + private _requireValidFileName(fileName: string | undefined): asserts fileName is string { + if (!fileName) throw new DotLottieError('Invalid audio fileName'); + } +} diff --git a/packages/dotlottie-js/src/v1/common/dotlottie.ts b/packages/dotlottie-js/src/v1/common/dotlottie.ts new file mode 100644 index 00000000..0aa8a711 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/dotlottie.ts @@ -0,0 +1,588 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; + +import { PACKAGE_NAME } from '../../constants'; +import type { ConversionOptions, GetAnimationOptions } from '../../types'; +import { DotLottieError, isAudioAsset, isImageAsset, isValidURL } from '../../utils'; + +import type { AnimationOptionsV1, LottieAnimationCommonV1 } from './animation'; +import type { LottieAudioCommonV1 } from './audio'; +import type { LottieImageCommonV1 } from './image'; +import type { DotLottieV1Plugin } from './plugin'; +import type { ManifestV1 } from './schemas/manifest'; + +export interface DotLottieV1Options { + author?: string; + description?: string; + enableDuplicateImageOptimization?: boolean; + generator?: string; + keywords?: string; + revision?: number; +} + +export class DotLottieCommonV1 { + protected readonly _animationsMap: Map = new Map(); + + protected readonly _plugins: DotLottieV1Plugin[] = []; + + protected _author: string = PACKAGE_NAME; + + protected _description: string | undefined; + + protected _generator: string = PACKAGE_NAME; + + protected _keywords: string | undefined; + + protected _version: string = '1'; + + protected _revision: number | undefined; + + // Custom data for the DotLottieV1 + protected _customData: Record | undefined; + + public enableDuplicateImageOptimization?: boolean; + + public constructor(options?: DotLottieV1Options) { + if (typeof options?.author === 'string') { + this._author = options.author; + } + + if (typeof options?.description === 'string') { + this._description = options.description; + } + + if (typeof options?.generator === 'string') { + this._generator = options.generator; + } + + if (typeof options?.keywords === 'string') { + this._keywords = options.keywords; + } + + if (typeof options?.revision === 'number') { + this._revision = options.revision; + } + + this.enableDuplicateImageOptimization = options?.enableDuplicateImageOptimization ?? false; + } + + public async toBase64(_options: ConversionOptions | undefined = undefined): Promise { + throw new DotLottieError('toBase64() method not implemented in concrete class!'); + } + + public create(_options?: DotLottieV1Options): DotLottieCommonV1 { + throw new DotLottieError('create() method not implemented in concrete class!'); + } + + public async download(_fileName: string, _options: ConversionOptions | undefined = undefined): Promise { + throw new DotLottieError('download(fileName:string) method not implemented in concrete class!'); + } + + public addPlugins(..._plugins: DotLottieV1Plugin[]): DotLottieCommonV1 { + throw new DotLottieError('addPlugins(...plugins: DotLottieV1Plugin[]) not implemented in concrete class!'); + } + + public addAnimation(_animationOptions: AnimationOptionsV1): DotLottieCommonV1 { + throw new DotLottieError('addAnimation(animationOptions: AnimationOptions) not implemented in concrete class!'); + } + + public async fromArrayBuffer(_arrayBuffer: ArrayBuffer): Promise { + throw new DotLottieError( + 'fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise not implemented in concrete class!', + ); + } + + public async toArrayBuffer(_options: ConversionOptions | undefined = undefined): Promise { + throw new DotLottieError('toArrayBuffer(): Promise is not implemented in concrete class!'); + } + + public get plugins(): DotLottieV1Plugin[] { + return this._plugins; + } + + public get version(): string { + return this._version; + } + + public get revision(): number | undefined { + return this._revision; + } + + public get author(): string { + return this._author; + } + + public get description(): string | undefined { + return this._description; + } + + public get keywords(): string | undefined { + return this._keywords; + } + + public get generator(): string { + return this._generator; + } + + public get animations(): LottieAnimationCommonV1[] { + return Array.from(this._animationsMap.values()); + } + + public get manifest(): ManifestV1 { + return this._buildManifest(); + } + + public get custom(): Record | undefined { + return this._customData; + } + + public setCustomData(customData: Record | undefined): DotLottieCommonV1 { + this._customData = customData ?? {}; + + return this; + } + + public setAuthor(author: string): DotLottieCommonV1 { + this._author = author; + + return this; + } + + public setDescription(description: string | undefined): DotLottieCommonV1 { + this._description = typeof description === 'string' ? description : ''; + + return this; + } + + public setKeywords(keywords: string | undefined): DotLottieCommonV1 { + this._keywords = typeof keywords === 'string' ? keywords : 'DotLottieV1'; + + return this; + } + + public setRevision(revision: number): DotLottieCommonV1 { + this._revision = revision; + + return this; + } + + /** + * Renames the underlying LottieImageV1, as well as updating the image asset path inside the animation data. + * @param newName - desired id and fileName, + * @param imageId - The id of the LottieImageV1 to rename + */ + private async _renameImage( + animation: LottieAnimationCommonV1, + newLottieAssetId: string, + lottieAssetId: string, + ): Promise { + for (const imageAsset of animation.imageAssets) { + if (imageAsset.lottieAssetId === lottieAssetId) { + // Rename the LottieImageV1 + const oldPath = imageAsset.fileName; + + await imageAsset.renameImage(newLottieAssetId); + + if (!animation.data) throw new DotLottieError('No animation data available.'); + + const animationAssets = animation.data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('No image assets to rename.'); + + // Find the image asset inside the animation data and rename its path + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset) { + if (asset.p === oldPath) { + asset.p = imageAsset.fileName; + } + } + } + } + } + } + + /** + * Generates a map of duplicate image ids and their count. + * @returns Map of duplicate image ids and their count. + */ + private _generateMapOfOccurencesFromImageIds(): Map { + const dupeMap = new Map(); + + this.animations.forEach((animation) => { + animation.imageAssets.forEach((imageAsset) => { + if (dupeMap.has(imageAsset.lottieAssetId)) { + const count = dupeMap.get(imageAsset.lottieAssetId) ?? 0; + + dupeMap.set(imageAsset.lottieAssetId, count + 1); + } else { + dupeMap.set(imageAsset.lottieAssetId, 1); + } + }); + }); + + return dupeMap; + } + + /** + * Renames the image assets in all animations to avoid conflicts. + * + * Steps: + * - Generate how many times across all animations the same image id has been used. + * - Loop through every animation in reverse order + * - Every time an animation uses an image asset that is also used elsewhere, append the count to the image's asset id and then decrement. + * + * Result of renaming for every animation: + * + * - Inside the Lottie's data and it's Asset object: + * - The Asset id stays the same, meaning that every reference to the asset is still valid (refId) + * - The path is changed to the new asset id with the format \{assetId\}_\{count\} + * + * - On the dotLottie file system scope: + * - The image file name is changed to the new asset id \{assetId\}_\{count\}.\{ext\} + */ + private async _renameImageAssets(): Promise { + const occurenceMap = this._generateMapOfOccurencesFromImageIds(); + + // Loop over every animation + for (let i = this.animations.length - 1; i >= 0; i -= 1) { + const animation = this.animations.at(i); + + if (animation) { + // Loop over every image asset of the animation + for (let j = animation.imageAssets.length - 1; j >= 0; j -= 1) { + const image = animation.imageAssets.at(j); + + if (image) { + // Get how many times the same image id has been used + let count = occurenceMap.get(image.lottieAssetId) ?? 0; + + if (count > 0) { + count -= 1; + } + + // Decrement the count + occurenceMap.set(image.lottieAssetId, count); + + if (count > 0) { + // Rename the with n-1 count + await this._renameImage(animation, `${image.lottieAssetId}_${count}`, image.lottieAssetId); + } + } + } + } + } + } + + /** + * Renames the underlying LottieAudioV1, as well as updating the audio asset path inside the animation data. + * @param newName - desired id and fileName, + * @param audioId - The id of the LottieAudioV1 to rename + */ + private async _renameAudio(animation: LottieAnimationCommonV1, newName: string, audioId: string): Promise { + for (const audioAsset of animation.audioAssets) { + if (audioAsset.id === audioId) { + // Rename the LottieImageV1 + await audioAsset.renameAudio(newName); + + if (!animation.data) throw new DotLottieError('No animation data available.'); + + const animationAssets = animation.data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('No audio assets to rename.'); + + // Find the audio asset inside the animation data and rename its path + for (const asset of animationAssets) { + if (isAudioAsset(asset)) { + if (asset.id === audioId) { + asset.p = audioAsset.fileName; + } + } + } + } + } + } + + private async _renameAudioAssets(): Promise { + const audio: Map = new Map(); + + this.animations.forEach((animation) => { + audio.set(animation.id, animation.audioAssets); + }); + + let size = 0; + + audio.forEach((value) => { + size += value.length; + }); + + for (let i = this.animations.length - 1; i >= 0; i -= 1) { + const animation = this.animations.at(i); + + if (animation) { + for (let j = animation.audioAssets.length - 1; j >= 0; j -= 1) { + const audioAsset = animation.audioAssets.at(j); + + if (audioAsset) { + await this._renameAudio(animation, `audio_${size}`, audioAsset.id); + size -= 1; + } + } + } + } + } + + protected _addLottieAnimation(animation: LottieAnimationCommonV1): DotLottieCommonV1 { + if (this._animationsMap.get(animation.id)) { + throw new DotLottieError('Duplicate animation id detected, aborting.'); + } + + this._animationsMap.set(animation.id, animation); + + return this; + } + + /** + * Inlines all assets of the passed animation + * @param animation - Animation whose asset are to be inlined + * @returns LottieAnimationCommonV1 with inlined assets + */ + private async _findAssetsAndInline(animation: LottieAnimationCommonV1): Promise { + const animationAssets = animation.data?.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError("Failed to inline assets, the animation's assets are undefined."); + + const images = this.getImages(); + const audios = this.getAudio(); + + for (const asset of animationAssets) { + if (isImageAsset(asset)) { + for (const image of images) { + if (image.fileName === asset.p) { + // encoded is true + asset.e = 1; + asset.u = ''; + asset.p = await image.toDataURL(); + } + } + } else if (isAudioAsset(asset)) { + for (const audio of audios) { + if (audio.fileName === asset.p) { + // encoded is true + asset.e = 1; + asset.u = ''; + asset.p = await audio.toDataURL(); + } + } + } + } + + return animation; + } + + /** + * Returns the desired animation + * @param animationId - desired animation id + * @param inlineAssets - if true will inline the assets inside the data of the LottieAnimationV1 + * @returns + */ + public async getAnimation( + animationId: string, + options?: GetAnimationOptions, + ): Promise { + if (!options?.inlineAssets) return this._animationsMap.get(animationId); + + let dataWithInlinedImages = this._animationsMap.get(animationId); + + if (!dataWithInlinedImages) throw new DotLottieError('Failed to find animation.'); + + dataWithInlinedImages = await this._findAssetsAndInline(dataWithInlinedImages); + + return dataWithInlinedImages; + } + + public getAnimations(): Array<[string, LottieAnimationCommonV1]> | undefined { + return Array.from(this._animationsMap); + } + + public removeAnimation(animationId: string): DotLottieCommonV1 { + const targetAnimation = this._animationsMap.get(animationId); + + if (targetAnimation) { + this._animationsMap.delete(targetAnimation.id); + } + + return this; + } + + public getImages(): LottieImageCommonV1[] { + const images: LottieImageCommonV1[] = []; + + this.animations.map((animation) => { + return images.push(...animation.imageAssets); + }); + + return images; + } + + public getAudio(): LottieAudioCommonV1[] { + const audio: LottieAudioCommonV1[] = []; + + this.animations.map((animation) => { + return audio.push(...animation.audioAssets); + }); + + return audio; + } + + protected _buildManifest(): ManifestV1 { + const animationsList = Array.from(this._animationsMap.values()).map((animation) => ({ + id: animation.id, + ...(animation.autoplay !== undefined && { autoplay: animation.autoplay }), + ...(animation.loop !== undefined && { loop: animation.loop }), + ...(animation.speed !== undefined && { speed: animation.speed }), + ...(animation.direction !== undefined && { direction: animation.direction }), + ...(animation.playMode !== undefined && { playMode: animation.playMode }), + ...(animation.hover !== undefined && { hover: animation.hover }), + ...(animation.intermission !== undefined && { intermission: animation.intermission }), + ...(animation.themeColor !== undefined && { themeColor: animation.themeColor }), + })); + + const manifest: ManifestV1 = { + version: this.version, + generator: this.generator, + author: this.author, + ...(this.keywords !== undefined && { keywords: this.keywords }), + ...(this.revision !== undefined && { revision: this.revision }), + animations: animationsList, + ...(this.description && this.description.trim() !== '' ? { description: this.description } : {}), + ...(this._customData && Object.keys(this._customData).length !== 0 ? { custom: this._customData } : {}), + }; + + return manifest; + } + + /** + * Constructs the manifest and calls toJSON on the animations + * so the data is fetched for every animation. + * + * @returns DotLottieV1 context + */ + public async build(): Promise { + this._buildManifest(); + + for (const animation of this.animations) { + await animation.toJSON(); + } + + if (this.animations.length > 1) { + // Rename assets incrementally if there are multiple animations + await this._renameImageAssets(); + await this._renameAudioAssets(); + } + + const parallelPlugins = []; + const sequentialPlugins = []; + + for (const plugin of this.plugins) { + if (plugin.parallel) { + parallelPlugins.push(plugin); + } else { + sequentialPlugins.push(plugin); + } + } + + // Run parallel plugins + await Promise.all(parallelPlugins.map(async (plugin) => plugin.onBuild())); + + // Run sequential plugins + for (const plugin of sequentialPlugins) { + await plugin.onBuild(); + } + + return this; + } + + public async toBlob(options: ConversionOptions | undefined = undefined): Promise { + const arrayBuffer = await this.toArrayBuffer(options); + + return new Blob([arrayBuffer], { type: 'application/zip' }); + } + + /** + * Creates a DotLottieV1 instance from a url to a DotLottieV1 file + * @param url - url to the DotLottieV1 file + * @returns DotLottieV1 instance + */ + public async fromURL(url: string): Promise { + if (!isValidURL(url)) throw new DotLottieError('Invalid URL'); + + try { + const response = await fetch(url); + + if (!response.ok) { + throw new DotLottieError(response.statusText); + } + + const arrayBuffer = await response.arrayBuffer(); + + return this.fromArrayBuffer(arrayBuffer); + } catch (err) { + if (err instanceof Error) { + throw new DotLottieError(err.message); + } + } + + throw new DotLottieError('Unknown error'); + } + + public merge(...DotLottieV1s: DotLottieCommonV1[]): DotLottieCommonV1 { + const mergedDotLottieV1 = this.create(); + + for (const dotLottieV1 of DotLottieV1s) { + dotLottieV1.animations.forEach((animation) => { + if (animation.data) { + mergedDotLottieV1.addAnimation({ + id: animation.id, + data: animation.data, + }); + } else if (animation.url) { + mergedDotLottieV1.addAnimation({ + id: animation.id, + url: animation.url, + }); + } + }); + } + + return mergedDotLottieV1; + } + + protected _requireValidAuthor(author: string | undefined): asserts author is string { + if (typeof author !== 'string') throw new DotLottieError('Invalid author'); + } + + protected _requireValidDescription(description: string | undefined): asserts description is string { + if (typeof description !== 'string') throw new DotLottieError('Invalid description'); + } + + protected _requireValidGenerator(generator: string | undefined): asserts generator is string { + if (typeof generator !== 'string') throw new DotLottieError('Invalid generator'); + } + + protected _requireValidKeywords(keywords: string | undefined): asserts keywords is string { + if (typeof keywords !== 'string') throw new DotLottieError('Invalid keywords'); + } + + protected _requireValidVersion(version: string | undefined): asserts version is string { + if (typeof version !== 'string') throw new DotLottieError('Invalid version'); + } + + protected _requireValidCustomData( + customData: Record | undefined, + ): asserts customData is Record { + if (!customData) throw new DotLottieError('Invalid customData'); + } +} diff --git a/packages/dotlottie-js/src/v1/common/image.ts b/packages/dotlottie-js/src/v1/common/image.ts new file mode 100644 index 00000000..d124862e --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/image.ts @@ -0,0 +1,238 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ZipOptions } from 'fflate'; + +import type { ImageData } from '../../types'; +import { dataUrlFromU8, DotLottieError, getExtensionTypeFromBase64 } from '../../utils'; + +import type { LottieAnimationCommonV1 } from './animation'; + +export interface ImageOptionsV1 { + data?: ImageData; + fileName: string; + id: string; + lottieAssetId: string; + parentAnimations?: LottieAnimationCommonV1[]; + zipOptions?: ZipOptions; +} + +export class LottieImageCommonV1 { + protected _data?: ImageData; + + /** + * Unique id for the LottieImageCommon object. This is never modified. + */ + protected _id: string = ''; + + /** + * Asset id representing the image asset inside the Lottie animation. This can be modified. + */ + protected _lottieAssetId: string = ''; + + protected _fileName: string = ''; + + protected _parentAnimations: LottieAnimationCommonV1[]; + + protected _zipOptions: ZipOptions; + + public constructor(options: ImageOptionsV1) { + this._requireValidId(options.id); + this._requireValidLottieAssetId(options.lottieAssetId); + this._requireValidFileName(options.fileName); + + this._zipOptions = options.zipOptions ?? {}; + + if (options.data) { + this._data = options.data; + } + + if (options.id) { + this._id = options.id; + } + + if (options.lottieAssetId) { + this._lottieAssetId = options.lottieAssetId; + } + + if (options.fileName) { + this._fileName = options.fileName; + } + + this._parentAnimations = options.parentAnimations || []; + } + + public get zipOptions(): ZipOptions { + return this._zipOptions; + } + + public set zipOptions(zipOptions: ZipOptions) { + this._zipOptions = zipOptions; + } + + /** + * Ensure that the provided id is a valid string. + * The id must be a non-empty string, otherwise an error will be thrown. + * @param id - The id to validate. + * @throws Error - if the id is not a valid string. + */ + private _requireValidId(id: string | undefined): asserts id is string { + if (!id) throw new DotLottieError('Invalid image id'); + } + + /** + * Ensure that the provided id is a valid string. + * The id must be a non-empty string, otherwise an error will be thrown. + * @param id - The id to validate. + * @throws Error - if the id is not a valid string. + */ + private _requireValidLottieAssetId(id: string | undefined): asserts id is string { + if (!id) throw new DotLottieError('Invalid Lottie Image Asset Id'); + } + + /** + * Ensure that the provided fileName is a valid string. + * The fileName must be a non-empty string, otherwise an error will be thrown. + * @param fileName - The fileName to validate. + * @throws Error - if the fileName is not a valid string. + */ + private _requireValidFileName(fileName: string | undefined): asserts fileName is string { + if (!fileName) throw new DotLottieError('Invalid image fileName'); + } + + public get fileName(): string { + return this._fileName; + } + + public set fileName(fileName: string) { + this._requireValidFileName(fileName); + + this._fileName = fileName; + } + + public get id(): string { + return this._id; + } + + public set id(id: string) { + this._requireValidId(id); + + this._id = id; + } + + public get lottieAssetId(): string { + return this._lottieAssetId; + } + + public set lottieAssetId(id: string) { + this._requireValidLottieAssetId(id); + this._lottieAssetId = id; + } + + public get data(): ImageData | undefined { + return this._data; + } + + public set data(data: ImageData | undefined) { + if (!data) { + throw new DotLottieError('Invalid data'); + } + + this._data = data; + } + + public get parentAnimations(): LottieAnimationCommonV1[] { + return this._parentAnimations; + } + + public set parentAnimations(parentAnimations: LottieAnimationCommonV1[]) { + this._parentAnimations = parentAnimations; + } + + public async toDataURL(): Promise { + if (this._data && this._isDataURL(this._data)) return this.data as string; + + const arrayBuffer = await this.toArrayBuffer(); + + return dataUrlFromU8(new Uint8Array(arrayBuffer)); + } + + /** + * Renames the lottieAssetId and fileName to newName. + * @param newName - A new lottieAssetId and filename for the image. + */ + public async renameImage(newLottieAssetId: string): Promise { + this._lottieAssetId = newLottieAssetId; + + const data = await this.toDataURL(); + + const ext = await getExtensionTypeFromBase64(data); + + if (!ext) { + throw new DotLottieError('File extension type could not be detected from asset file.'); + } + + this.fileName = `${newLottieAssetId}.${ext}`; + } + + public async toArrayBuffer(): Promise { + const blob = await (await this.toBlob()).arrayBuffer(); + + return blob; + } + + public async toBlob(): Promise { + if (!this._data) { + throw new DotLottieError('Invalid image data.'); + } + + if (this._isDataURL(this._data)) { + const data = this._data as string; + + const [header, base64] = data.split(','); + + // If the data doesnt contain the encoding URL, return it + if ((!header || !base64) && data.length) { + return new Blob([data]); + } + + if (!header || !base64) { + throw new DotLottieError('Invalid image data.'); + } + + // eslint-disable-next-line require-unicode-regexp + const type = header.replace('data:', '').replace(/;base64$/, ''); + + return new Blob([base64], { type }); + } + + if (this._isArrayBuffer(this._data)) { + return new Blob([this._data]); + } + + if (this._isBlob(this._data)) { + return this._data as Blob; + } + + throw new DotLottieError('Invalid image data.'); + } + + protected async _fromUrlToBlob(url: string): Promise { + const response = await fetch(url); + + return response.blob(); + } + + protected _isArrayBuffer(data: ImageData): boolean { + return data instanceof ArrayBuffer; + } + + protected _isDataURL(data: ImageData): boolean { + return typeof data === 'string' && data.startsWith('data:'); + } + + protected _isBlob(data: ImageData): boolean { + return data instanceof Blob; + } +} diff --git a/packages/dotlottie-js/src/v1/common/index.ts b/packages/dotlottie-js/src/v1/common/index.ts new file mode 100644 index 00000000..c633b749 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/index.ts @@ -0,0 +1,9 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './audio'; +export * from './schemas'; diff --git a/packages/dotlottie-js/src/v1/common/plugin.ts b/packages/dotlottie-js/src/v1/common/plugin.ts new file mode 100644 index 00000000..9e1753e3 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/plugin.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import { DotLottieError } from '../../utils'; + +import type { DotLottieCommonV1 } from './dotlottie'; + +interface DotLottieV1PluginOptions { + parallel?: boolean; +} + +export class DotLottieV1Plugin { + protected dotLottieV1: DotLottieCommonV1 | undefined; + + protected _parallel: boolean = false; + + public constructor(options?: DotLottieV1PluginOptions) { + this.dotLottieV1 = undefined; + + if (options?.parallel) { + this._parallel = options.parallel; + } + } + + public install(dotLottieV1: DotLottieCommonV1): void { + this.dotLottieV1 = dotLottieV1; + } + + public uninstall(): void { + this.dotLottieV1 = undefined; + } + + public get parallel(): boolean { + return this._parallel; + } + + public set parallel(value: boolean) { + this._parallel = value; + } + + public async onBuild(): Promise { + throw new DotLottieError('DotLottieV1-plugin build Not implemented!'); + } + + protected _requireDotLottieV1(dotLottieV1: DotLottieCommonV1 | undefined): asserts dotLottieV1 { + if (!dotLottieV1) + throw new DotLottieError('DotLottieV1 context is null inside of duplicate image detector plugin.'); + } +} diff --git a/packages/dotlottie-js/src/v1/common/plugins/duplicate-image-detector.ts b/packages/dotlottie-js/src/v1/common/plugins/duplicate-image-detector.ts new file mode 100644 index 00000000..71fd2b64 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/plugins/duplicate-image-detector.ts @@ -0,0 +1,179 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; + +import { DotLottieError } from '../../../utils'; +import { LottieImageV1 } from '../../browser/image'; +import type { LottieAnimationCommonV1 } from '../animation'; +import type { LottieImageCommonV1 } from '../image'; +import { DotLottieV1Plugin } from '../plugin'; + +interface LottieImageV1Compare { + excludeFromExport: boolean; + hash: string | undefined; + image: LottieImageCommonV1; +} + +export class DuplicateImageDetectorCommon extends DotLottieV1Plugin { + public async generatePhash(_image: LottieImageCommonV1): Promise { + throw new DotLottieError( + 'generatePhash(image: LottieImageCommonV1): Promise is not implemented in concrete class.', + ); + } + + public distanceTo(_imageHash: string, _targetImageHash: string): number { + throw new DotLottieError( + 'distanceTo(_imageHash: string, _targetImageHash: string): Promise is not implemented in concrete class.', + ); + } + + private async _createRecordOfDuplicates(): Promise> { + this._requireDotLottieV1(this.dotLottieV1); + + const images: LottieImageV1Compare[] = []; + const recordOfDuplicates: Record = {}; + + // push all of the animation image assets in to the images array + for (const animation of this.dotLottieV1.animations) { + for (const image of animation.imageAssets) { + images.push({ + excludeFromExport: false, + image, + hash: await this.generatePhash(image), + }); + } + } + + // For every array of images + // Loop over the images of the array + for (const image of images) { + // Now that we have a single image of the image array, compare it to every other image in the arry + for (const compareImage of images) { + if ( + image.image.lottieAssetId !== compareImage.image.lottieAssetId && + !image.excludeFromExport && + !compareImage.excludeFromExport && + image.hash && + compareImage.hash && + this.distanceTo(image.hash, compareImage.hash) < 5 + // image.hash.getHammingDistance(compareImage.hash) < 5 + ) { + // Check if key is already in use + if (!recordOfDuplicates[image.image.fileName] && !recordOfDuplicates[compareImage.image.fileName]) { + compareImage.excludeFromExport = true; + + recordOfDuplicates[image.image.fileName] = [compareImage.image]; + } else if (recordOfDuplicates[compareImage.image.fileName]) { + // Check for duplicates, otherwise push the duplicate image + if ( + !recordOfDuplicates[compareImage.image.fileName]?.find( + (item) => item.lottieAssetId === image.image.lottieAssetId, + ) + ) { + image.excludeFromExport = true; + recordOfDuplicates[compareImage.image.fileName]?.push(image.image); + } + } + } + } + } + + return recordOfDuplicates; + } + + /** + * Apply the image path to all duplicate images. + * + * @param recordOfDuplicates - A record of duplicate images, the key being a fileName, + * the value being the identical LottieImageCommonV1 object. + */ + public adjustDuplicateImageAssetPath( + animation: LottieAnimationCommonV1, + recordOfDuplicates: Record, + ): void { + for (const key in recordOfDuplicates) { + if (key) { + recordOfDuplicates[key]?.forEach((item) => { + // Check if this animation has the image before loop over data to save time ? + + if (animation.data) { + const animationAssets = animation.data.assets as AnimationType['assets']; + + if (animationAssets) { + animationAssets.forEach((asset) => { + if ('w' in asset && 'h' in asset) { + // we've found an asset id thats equal to the id of a duplicate image + // Or we found the fileName of a duplicate inside the asset, so we need to change it + + if (asset.p === item.fileName) { + const fileName = key; + + asset.p = fileName; + } + } + }); + } + } + }); + } + } + } + + public override async onBuild(): Promise { + this._requireDotLottieV1(this.dotLottieV1); + + // Create a record of duplicates + const recordOfDuplicates: Record = await this._createRecordOfDuplicates(); + + // Check the record of duplicates and repath the duplicate images + this.dotLottieV1.animations.forEach((animation) => { + this.adjustDuplicateImageAssetPath(animation, recordOfDuplicates); + }); + + // Create an array of duplicates by looping over the recordOfDuplicates and using the key as the image to use + const clonedImages: Record = {}; + const images = this.dotLottieV1.getImages(); + + for (const key in recordOfDuplicates) { + if (key) { + for (const image of images) { + if (image.fileName === key && image.data !== undefined) { + clonedImages[key] = new LottieImageV1({ + data: image.data, + id: image.id, + lottieAssetId: image.lottieAssetId, + fileName: image.fileName, + }); + } + } + } + } + + if (Object.keys(clonedImages).length !== Object.keys(recordOfDuplicates).length) { + throw new DotLottieError('The number of cloned images does not match the number of duplicate keys.'); + } + + // For each image of recordOfDuplicates, remove itself from all the parent animations and push the clone + for (const key in recordOfDuplicates) { + if (key) { + recordOfDuplicates[key]?.forEach((image) => { + if (image.parentAnimations.length) { + for (const parentAnimation of image.parentAnimations) { + parentAnimation.imageAssets.splice(parentAnimation.imageAssets.indexOf(image), 1); + + const clonedImage = clonedImages[key]; + + if (clonedImage !== undefined) { + parentAnimation.imageAssets.push(clonedImage); + + clonedImage.parentAnimations.push(parentAnimation); + } + } + } + }); + } + } + } +} diff --git a/packages/dotlottie-js/src/v1/common/schemas/index.ts b/packages/dotlottie-js/src/v1/common/schemas/index.ts new file mode 100644 index 00000000..7caae102 --- /dev/null +++ b/packages/dotlottie-js/src/v1/common/schemas/index.ts @@ -0,0 +1,5 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './manifest'; diff --git a/packages/dotlottie-js/src/common/manifest.ts b/packages/dotlottie-js/src/v1/common/schemas/manifest.ts similarity index 60% rename from packages/dotlottie-js/src/common/manifest.ts rename to packages/dotlottie-js/src/v1/common/schemas/manifest.ts index 46e350ba..4bce0f2b 100644 --- a/packages/dotlottie-js/src/common/manifest.ts +++ b/packages/dotlottie-js/src/v1/common/schemas/manifest.ts @@ -1,5 +1,5 @@ /** - * Copyright 2023 Design Barn Inc. + * Copyright 2024 Design Barn Inc. */ import { @@ -24,38 +24,31 @@ export enum PlayMode { export const PlayModeSchema = nativeEnum(PlayMode); -export const ManifestAnimationSchema = object({ +export const ManifestAnimationSchemaV1 = object({ + id: string(), + autoplay: optional(boolean()), - defaultTheme: optional(string()), + loop: optional(union([boolean(), number()])), + speed: optional(number()), direction: optional(union([literal(1), literal(-1)])), + playMode: optional(PlayModeSchema), hover: optional(boolean()), - id: string(), intermission: optional(number()), - loop: optional(union([boolean(), number()])), - playMode: optional(PlayModeSchema), - speed: optional(number()), themeColor: optional(string()), }); -export type ManifestAnimation = Output; +export type ManifestAnimationV1 = Output; -export const ManifestThemeSchema = object({ - animations: array(string()), - id: string(), -}); -export type ManifestTheme = Output; +export const ManifestSchemaV1 = object({ + version: optional(string()), + generator: optional(string()), -export const ManifestSchema = object({ activeAnimationId: optional(string()), - animations: array(ManifestAnimationSchema), + animations: array(ManifestAnimationSchemaV1), author: optional(string()), custom: optional(record(string(), any())), description: optional(string()), - generator: optional(string()), keywords: optional(string()), revision: optional(number()), - themes: optional(array(ManifestThemeSchema)), - states: optional(array(string())), - version: optional(string()), }); -export type Manifest = Output; +export type ManifestV1 = Output; diff --git a/packages/dotlottie-js/src/v1/index.browser.ts b/packages/dotlottie-js/src/v1/index.browser.ts new file mode 100644 index 00000000..0e6df446 --- /dev/null +++ b/packages/dotlottie-js/src/v1/index.browser.ts @@ -0,0 +1,6 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './browser'; +export * from './common'; diff --git a/packages/dotlottie-js/src/v1/index.node.ts b/packages/dotlottie-js/src/v1/index.node.ts new file mode 100644 index 00000000..f4986472 --- /dev/null +++ b/packages/dotlottie-js/src/v1/index.node.ts @@ -0,0 +1,6 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './node'; +export * from './common'; diff --git a/packages/dotlottie-js/src/v1/node/animation.ts b/packages/dotlottie-js/src/v1/node/animation.ts new file mode 100644 index 00000000..1c89a18a --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/animation.ts @@ -0,0 +1,129 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; + +import { DotLottieError, getExtensionTypeFromBase64, isAudioAsset } from '../../utils'; +import type { AnimationOptionsV1 } from '../common'; +import { LottieAnimationCommonV1 } from '../common'; + +import { LottieAudioV1 } from './audio'; +import { LottieImageV1 } from './image'; + +export class LottieAnimationV1 extends LottieAnimationCommonV1 { + public constructor(options: AnimationOptionsV1) { + super(options); + } + + /** + * Return the animation data as a base64 encoded string. + * + * @returns data - The animation data as a base64 encoded string. + * @throws Error - if the animation data is not set and the url is not provided. + * @throws Error - if the animation data is not a valid Lottie animation data object. + * @throws Error - if the fetch request fails. + */ + public override async toBase64(): Promise { + const data = await this.toArrayBuffer(); + + return Buffer.from(data).toString('base64'); + } + + /** + * + * Extract image assets from the animation. + * + * @returns boolean - true on error otherwise false on success + */ + protected override async _extractImageAssets(): Promise { + if (!this._data) throw new DotLottieError('Failed to extract image assets: Animation data does not exist'); + + const animationAssets = this._data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); + + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset && !('xt' in asset) && 'p' in asset) { + const imageData = asset.p.split(','); + + // Image data is invalid + if (!imageData.length || !imageData[0] || !imageData[1]) { + break; + } + + let extType = null; + const fileType = await getExtensionTypeFromBase64(asset.p); + + if (fileType) { + extType = fileType; + + const fileName = `${asset.id}.${extType}`; + + this._imageAssets.push( + new LottieImageV1({ + data: asset.p, + id: asset.id, + lottieAssetId: asset.id, + fileName, + parentAnimations: [this], + }), + ); + + asset.p = fileName; + asset.u = '/images/'; + asset.e = 0; + } + } + } + + return false; + } + + /** + * + * Extract audio assets from the animation. + * + * @returns boolean - true on error otherwise false on success + */ + protected override async _extractAudioAssets(): Promise { + if (!this._data) throw new DotLottieError('Failed to extract audio assets: Animation data does not exist'); + + const animationAssets = this._data.assets as AnimationType['assets']; + + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); + + for (const asset of animationAssets) { + if (isAudioAsset(asset)) { + const audioData = asset.p.split(','); + + // Audio data is invalid + if (!audioData.length || !audioData[0] || !audioData[1]) { + break; + } + + let extType = null; + const fileType = await getExtensionTypeFromBase64(asset.p); + + extType = fileType; + + const fileName = `${asset.id}.${extType}`; + + this._audioAssets.push( + new LottieAudioV1({ + data: asset.p, + id: asset.id, + fileName, + parentAnimations: [this], + }), + ); + + asset.p = fileName; + asset.u = '/audio/'; + asset.e = 0; + } + } + + return false; + } +} diff --git a/packages/dotlottie-js/src/v1/node/audio.ts b/packages/dotlottie-js/src/v1/node/audio.ts new file mode 100644 index 00000000..b2617af4 --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/audio.ts @@ -0,0 +1,12 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { AudioOptionsV1 } from '../common'; +import { LottieAudioCommonV1 } from '../common'; + +export class LottieAudioV1 extends LottieAudioCommonV1 { + public constructor(options: AudioOptionsV1) { + super(options); + } +} diff --git a/packages/dotlottie-js/src/v1/node/dotlottie.ts b/packages/dotlottie-js/src/v1/node/dotlottie.ts new file mode 100644 index 00000000..e126782a --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/dotlottie.ts @@ -0,0 +1,324 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @typescript-eslint/no-use-before-define */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import type { Zippable } from 'fflate'; +import { strToU8, unzip, zip, strFromU8 } from 'fflate'; + +import type { ConversionOptions } from '../../types'; +import { + base64ToUint8Array, + DotLottieError, + getDotLottieVersion, + getExtensionTypeFromBase64, + isAudioAsset, +} from '../../utils'; +import { DotLottie } from '../../v2/node'; +import type { DotLottieV1Options, AnimationOptionsV1 } from '../common'; +import { DotLottieCommonV1 } from '../common'; +import type { ManifestV1 } from '../common/schemas/manifest'; + +import { LottieAnimationV1 } from './animation'; +import { LottieAudioV1 } from './audio'; +import { LottieImageV1 } from './image'; +import { DuplicateImageDetector } from './plugins/duplicate-image-detector'; + +export async function toDotLottieV1(arrayBuffer: ArrayBuffer): Promise { + const version = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (version === '2') { + const dotLottieV1 = new DotLottieV1(); + + const dotLottieV2 = await new DotLottie().fromArrayBuffer(arrayBuffer); + + const animationIds = dotLottieV2.animations.map((animation) => animation.id); + + for (const animationId of animationIds) { + const animation = await dotLottieV2.getAnimation(animationId, { inlineAssets: true }); + + if (animation && animation.data) { + dotLottieV1.addAnimation({ + data: animation.data, + id: animationId, + }); + } + } + + await dotLottieV1.build(); + + return dotLottieV1; + } else { + return new DotLottieV1().fromArrayBuffer(arrayBuffer); + } +} + +export class DotLottieV1 extends DotLottieCommonV1 { + public constructor(options?: DotLottieV1Options) { + super(options); + + if (this.enableDuplicateImageOptimization) { + const plugin = new DuplicateImageDetector(); + + plugin.install(this); + + this._plugins.push(plugin); + } + } + + public override create(): DotLottieCommonV1 { + return new DotLottieV1(); + } + + public override async toBase64(options?: ConversionOptions): Promise { + const data = await this.toArrayBuffer(options); + + return Buffer.from(data).toString('base64'); + } + + public override async download(_fileName: string, _options?: ConversionOptions): Promise { + throw new DotLottieError('Cannot download dotlottie in a non-browser environment'); + } + + public override addAnimation(animationOptions: AnimationOptionsV1): DotLottieCommonV1 { + const animation = new LottieAnimationV1(animationOptions); + + if (this._animationsMap.get(animationOptions.id)) { + throw new DotLottieError('Duplicate animation id detected, aborting.'); + } + + this._animationsMap.set(animation.id, animation); + + return this; + } + + public override async toArrayBuffer(options?: ConversionOptions): Promise { + const manifest = this._buildManifest(); + + const dotLottie: Zippable = { + 'manifest.json': [strToU8(JSON.stringify(manifest)), {}], + }; + + for (const animation of this.animations) { + const json = await animation.toJSON(); + + dotLottie[`animations/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; + + const imageAssets = animation.imageAssets; + const audioAssets = animation.audioAssets; + + for (const asset of imageAssets) { + // Assure we have a base64 encoded version of the image + const dataAsString = await asset.toDataURL(); + + dotLottie[`images/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + } + for (const asset of audioAssets) { + // Assure we have a base64 encoded version of the audio + const dataAsString = await asset.toDataURL(); + + dotLottie[`audio/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + } + } + + const DotLottieV1ArrayBuffer = await new Promise((resolve, reject) => { + zip(dotLottie, options?.zipOptions || {}, (err, data) => { + if (err) { + reject(err); + + return; + } + + resolve(data.buffer); + }); + }); + + return DotLottieV1ArrayBuffer; + } + + /** + * Creates a DotLottieV1 instance from an array buffer + * @param arrayBuffer - array buffer of the DotLottieV1 file + * @returns DotLottieV1 instance + * @throws Error + */ + public override async fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise { + const dotLottieVersion = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (dotLottieVersion === '2') { + return toDotLottieV1(arrayBuffer); + } + const dotLottie = new DotLottieV1(); + + try { + const contentObj = await new Promise((resolve, reject) => { + unzip(new Uint8Array(arrayBuffer), (err, data) => { + if (err) { + reject(err); + } + + resolve(data); + }); + }); + + const tmpImages = []; + const tmpAudio = []; + + if (contentObj['manifest.json'] instanceof Uint8Array) { + try { + const manifest = JSON.parse(strFromU8(contentObj['manifest.json'], false)) as ManifestV1; + const { author, custom, description, keywords } = manifest; + + if (author) { + this._requireValidAuthor(author); + dotLottie.setAuthor(author); + } + if (custom) { + this._requireValidCustomData(custom); + dotLottie.setCustomData(custom); + } + if (description) { + this._requireValidDescription(description); + dotLottie.setDescription(description); + } + if (keywords) { + this._requireValidKeywords(keywords); + dotLottie.setKeywords(keywords); + } + + for (const key of Object.keys(contentObj)) { + const decompressedFile = contentObj[key] as Uint8Array; + const decodedStr = strFromU8(contentObj[key] as Uint8Array, false); + + if (key.startsWith('animations/') && key.endsWith('.json')) { + // extract animationId from key as the key = `animations/${animationId}.json` + const animationId = /animations\/(.+)\.json/u.exec(key)?.[1]; + + if (!animationId) { + throw new DotLottieError('Invalid animation id'); + } + + const animation = JSON.parse(decodedStr); + + const animationSettings = manifest.animations.find((anim) => anim.id === animationId); + + if (animationSettings === undefined) { + throw new DotLottieError('Animation not found inside manifest'); + } + + dotLottie.addAnimation({ + data: animation, + ...animationSettings, + }); + } else if (key.startsWith('images/')) { + // extract imageId from key as the key = `images/${imageId}.${ext}` + const imageId = /images\/(.+)\./u.exec(key)?.[1]; + + if (!imageId) { + throw new DotLottieError('Invalid image id'); + } + + const base64 = Buffer.from(decompressedFile).toString('base64'); + + const ext = await getExtensionTypeFromBase64(base64); + + if (!ext) { + throw new DotLottieError('Unrecognized asset file format.'); + } + // Push the images in to a temporary array + const imgDataURL = `data:image/${ext};base64,${base64}`; + + tmpImages.push( + new LottieImageV1({ + id: imageId, + lottieAssetId: imageId, + data: imgDataURL, + fileName: key.split('/')[1] || '', + }), + ); + } else if (key.startsWith('audio/')) { + // Do audio extraction + // extract audioID from key as the key = `audio/${audioID}.${ext}` + const audioId = /audio\/(.+)\./u.exec(key)?.[1]; + + if (!audioId) { + throw new DotLottieError('Invalid audio id'); + } + + const base64 = Buffer.from(decompressedFile).toString('base64'); + + const ext = await getExtensionTypeFromBase64(base64); + + // Push the images in to a temporary array + const audioDataURL = `data:audio/${ext};base64,${base64}`; + + tmpAudio.push( + new LottieAudioV1({ + id: audioId, + data: audioDataURL, + fileName: key.split('/')[1] || '', + }), + ); + } + } + + // Go through the images and find to which animation they belong + for (const image of tmpImages) { + for (const parentAnimation of dotLottie.animations) { + if (parentAnimation.data) { + const animationAssets = parentAnimation.data.assets as AnimationType['assets']; + + if (animationAssets) { + for (const asset of animationAssets) { + if ('w' in asset && 'h' in asset) { + if (asset.p === image.fileName) { + image.parentAnimations.push(parentAnimation); + parentAnimation.imageAssets.push(image); + } + } + } + } + } + } + } + + // Go through the audio and find to which animation they belong + for (const audio of tmpAudio) { + for (const parentAnimation of dotLottie.animations) { + if (parentAnimation.data) { + const animationAssets = parentAnimation.data.assets as AnimationType['assets']; + + if (animationAssets) { + for (const asset of animationAssets) { + if (isAudioAsset(asset)) { + if (asset.p.includes(audio.id)) { + audio.parentAnimations.push(parentAnimation); + parentAnimation.audioAssets.push(audio); + } + } + } + } + } + } + } + } catch (err: unknown) { + // throw error as it's invalid json + throw new DotLottieError( + `Invalid manifest inside buffer! ${err instanceof Error ? err.message : 'Unknown error'}`, + ); + } + } else { + // throw error as it's invalid buffer + throw new DotLottieError('Invalid buffer'); + } + } catch (err: unknown) { + if (err instanceof Error) { + throw new DotLottieError(err.message); + } + } + + return dotLottie; + } +} diff --git a/packages/dotlottie-js/src/v1/node/image.ts b/packages/dotlottie-js/src/v1/node/image.ts new file mode 100644 index 00000000..a2ade2ee --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/image.ts @@ -0,0 +1,12 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ImageOptionsV1 } from '../common'; +import { LottieImageCommonV1 } from '../common'; + +export class LottieImageV1 extends LottieImageCommonV1 { + public constructor(options: ImageOptionsV1) { + super(options); + } +} diff --git a/packages/dotlottie-js/src/v1/node/index.ts b/packages/dotlottie-js/src/v1/node/index.ts new file mode 100644 index 00000000..b237f98b --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/index.ts @@ -0,0 +1,8 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './audio'; diff --git a/packages/dotlottie-js/src/v1/node/plugins/duplicate-image-detector.ts b/packages/dotlottie-js/src/v1/node/plugins/duplicate-image-detector.ts new file mode 100644 index 00000000..2b97d6ba --- /dev/null +++ b/packages/dotlottie-js/src/v1/node/plugins/duplicate-image-detector.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import phash from 'sharp-phash'; + +import { DotLottieError } from '../../../utils'; +import type { LottieImageCommonV1 } from '../../common'; +import { DuplicateImageDetectorCommon } from '../../common/plugins/duplicate-image-detector'; + +export class DuplicateImageDetector extends DuplicateImageDetectorCommon { + public override distanceTo(imageHash: string, targetImageHash: string): number { + let count = 0; + + for (let i = 0; i < targetImageHash.length; i += 1) { + if (targetImageHash[i] !== imageHash[i]) { + count += 1; + } + } + + return count; + } + + public override async generatePhash(image: LottieImageCommonV1): Promise { + if (!image.data) { + throw new DotLottieError("Can't generate phash value."); + } + + const nBuf = await image.toArrayBuffer(); + + const decoder = new TextDecoder(); + + let phashValue = null; + + phashValue = await phash(Buffer.from(decoder.decode(nBuf), 'base64')); + + return phashValue; + } +} diff --git a/packages/dotlottie-js/src/v2/__tests__/browser/animation.spec.ts b/packages/dotlottie-js/src/v2/__tests__/browser/animation.spec.ts new file mode 100644 index 00000000..b85da609 --- /dev/null +++ b/packages/dotlottie-js/src/v2/__tests__/browser/animation.spec.ts @@ -0,0 +1,291 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable no-new */ +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import { LottieAnimation } from '../../index.browser'; + +test('throws an error if it receives an invalid id when constructed', () => { + expect(() => { + new LottieAnimation({ id: '' }); + }).toThrow('Invalid animation id'); +}); + +test('throws an error if it receives an invalid url when constructed', () => { + const invalidUrl = 'xyz'; + + expect(() => { + new LottieAnimation({ id: 'test', url: invalidUrl }); + }).toThrow('Invalid animation url'); +}); + +test('throws an error if it receives an invalid lottie data when constructed', () => { + const invalidData = {} as AnimationType; + + expect(() => { + new LottieAnimation({ id: 'test', data: invalidData }); + }).toThrow('Received invalid Lottie data.'); +}); + +test('throws an error if it receives an no data or url when constructed', () => { + expect(() => { + new LottieAnimation({ id: 'test' }); + }).toThrow('No data or url provided.'); +}); + +test('gets and sets the zipOptions', () => { + const animation = new LottieAnimation({ + id: 'test', + data: animationData as unknown as AnimationType, + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(animation.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + animation.zipOptions = { + level: 1, + }; + + expect(animation.zipOptions).toEqual({ + level: 1, + }); +}); + +test('gets and sets the id', () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.id).toEqual('test'); + + animation.id = 'test2'; + + expect(animation.id).toEqual('test2'); +}); + +test('gets and sets the data', () => { + const animation = new LottieAnimation({ id: 'test', url: 'https://example.com' }); + + expect(animation.data).toBeUndefined(); + + animation.data = animationData as unknown as AnimationType; + + expect(animation.data).toEqual(animationData as unknown as AnimationType); +}); + +test('gets and sets the url', () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.url).toBeUndefined(); + + animation.url = 'https://example.com'; + + expect(animation.url).toEqual('https://example.com'); +}); + +describe('toJSON', () => { + test('returns the animation data as a JSON object', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const jsonData = await animation.toJSON(); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + }); + + test('returns the animation with inlined data as a JSON object', async () => { + const animation = new LottieAnimation({ + id: 'test', + data: structuredClone(BULL_DATA) as unknown as unknown as AnimationType, + }); + + const jsonData = await animation.toJSON({ inlineAssets: true }); + + expect(jsonData).toEqual(BULL_DATA as unknown as unknown as AnimationType); + }); + + test('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const jsonData = await animation.toJSON(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toJSON()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBase64', () => { + test('returns the base64 of the animation', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const dataUrl = await animation.toBase64(); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + }); + + test('resolves the animation data from the provided url and returns the animation data as a data url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const dataUrl = await animation.toBase64(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toBase64()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBlob', () => { + test('returns the animation data as a blob', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as a blob', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', + }); + + await expect(animation.toBlob()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toArrayBuffer', () => { + test('returns the animation data as an array buffer', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi.spyOn(window, 'fetch').mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toArrayBuffer()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/tests/lottie-audio-browser.spec.ts b/packages/dotlottie-js/src/v2/__tests__/browser/audio.spec.ts similarity index 77% rename from packages/dotlottie-js/src/tests/lottie-audio-browser.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/browser/audio.spec.ts index a17bf1f9..de602d27 100644 --- a/packages/dotlottie-js/src/tests/lottie-audio-browser.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/browser/audio.spec.ts @@ -2,14 +2,15 @@ * Copyright 2023 Design Barn Inc. */ -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +/* eslint-disable @lottiefiles/import-filename-format */ -import { DotLottie, LottieAudio, isAudioAsset } from '..'; +import type { Animation as AnimationType, Asset } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; -// eslint-disable-next-line @lottiefiles/import-filename-format -import AUDIO_ANIMATION_1_DATA from './__fixtures__/audio/instruments_1.json'; -// eslint-disable-next-line @lottiefiles/import-filename-format -import AUDIO_ANIMATION_2_DATA from './__fixtures__/audio/instruments_2.json'; +import AUDIO_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/audio/instruments_1.json'; +import AUDIO_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/audio/instruments_2.json'; +import { isAudioAsset } from '../../../utils'; +import { DotLottie, LottieAudio } from '../../index.browser'; describe('LottieAudio', () => { it('gets and sets the zipOptions', () => { @@ -55,15 +56,15 @@ describe('LottieAudio', () => { const expectedData: string[] = []; // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); @@ -93,8 +94,8 @@ describe('LottieAudio', () => { const expectedData: string[] = []; // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); diff --git a/packages/dotlottie-js/src/v2/__tests__/browser/dotlottie.spec.ts b/packages/dotlottie-js/src/v2/__tests__/browser/dotlottie.spec.ts new file mode 100644 index 00000000..64c71ddc --- /dev/null +++ b/packages/dotlottie-js/src/v2/__tests__/browser/dotlottie.spec.ts @@ -0,0 +1,891 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { strFromU8, unzipSync } from 'fflate'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import pkg from '../../../../package.json'; +import BALL_ANIMATION_DATA from '../../../__tests__/__fixtures__/ball.json'; +import BULL_ANIMATION_DATA from '../../../__tests__/__fixtures__/bull.json'; +import bullData from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import dotlottieAnimation from '../../../__tests__/__fixtures__/simple/animation.lottie?arraybuffer'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import manifest from '../../../__tests__/__fixtures__/simple/animation/manifest.json'; +import bigMergedDotLottie from '../../../__tests__/__fixtures__/simple/big-merged-dotlottie.lottie?arraybuffer'; +import editedDotlottieAnimation from '../../../__tests__/__fixtures__/simple/edited-settings.lottie?arraybuffer'; +import editedAnimationData from '../../../__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json'; +import editedManifest from '../../../__tests__/__fixtures__/simple/edited-settings/manifest.json'; +import type { AnimationData } from '../../../types'; +import type { AnimationOptions, ManifestAnimation } from '../../index.browser'; +import { DotLottie, LottieAnimation } from '../../index.browser'; + +describe('generator', () => { + test('has proper generator when no input provided', () => { + const dotLottie = new DotLottie(); + + expect(dotLottie.generator).toBe(`${pkg.name}@${pkg.version}`); + }); +}); + +describe('addAnimation', () => { + test('throws an error if it receives a duplicate id when constructed', () => { + expect(() => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + }).toThrow('Duplicate animation id detected, aborting.'); + }); + + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('adds an animation', () => { + const animationId = manifest.animations[0]?.id as string; + + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: animationId, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + const animation = dotlottie.animations[0]; + + expect(animation?.id).toBe(manifest.animations[0]?.id); + }); + + test('adds an animation using all customizable options', async () => { + const animationId = 'test_animation'; + + const dotlottie = new DotLottie(); + + const animationOptions: AnimationOptions = { + id: animationId, + data: animationData as unknown as AnimationType, + }; + + const manifestDataToCompare: ManifestAnimation = { + id: animationId, + }; + + dotlottie.addAnimation({ + ...animationOptions, + }); + + await dotlottie.build(); + + const animationManifest = dotlottie.manifest.animations[0]; + + expect(animationManifest).toEqual(manifestDataToCompare); + }); +}); + +describe('removeAnimation', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('removes an animation', () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + dotlottie.removeAnimation(manifest.animations[0]?.id as string); + + expect(dotlottie.animations.length).toBe(0); + }); +}); + +describe('getAnimation', () => { + test('returns animation instance', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); + + expect(animation).toBeInstanceOf(LottieAnimation); + + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(animationData as unknown as AnimationType); + }); + + test('returns undefined if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + const animation = await dotlottie.getAnimation('non_existent_animation'); + + expect(animation).toBeUndefined(); + }); + + test('returns animation instance with inlined assets', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: structuredClone(bullData) as unknown as AnimationData, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); + + expect(animation).toBeInstanceOf(LottieAnimation); + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(bullData as unknown as AnimationData); + }); + + test('adds multiple animations and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); + + dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, + }); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); + + test('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); + + await dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }) + .build(); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); +}); + +describe('download', () => { + test('throws error on node environment', async () => { + // skip test if running in browser environment + if (typeof window !== 'undefined') return; + + const dotlottie = new DotLottie(); + + expect( + dotlottie + .addAnimation({ + id: 'test_animation', + data: animationData as unknown as AnimationType, + }) + .download('file'), + ).rejects.toThrow('Cannot download dotlottie in a non-browser environment'); + }); + + test('downloads dotlottie file on browser', async () => { + // skip test if running in node environment + if (typeof window === 'undefined') return; + + const dotlottie = new DotLottie(); + + const fileName = 'test.lottie'; + + const fakeLink = document.createElement('a'); + + const clickSpy = vi.spyOn(fakeLink, 'click').mockImplementation(() => { + // do nothing + }); + + vi.spyOn(document, 'createElement').mockImplementation(() => { + return fakeLink; + }); + + const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL'); + + await dotlottie + .addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }) + .build(); + + await dotlottie.download(fileName); + + const blob = await dotlottie.toBlob(); + + expect(createObjectURLSpy).toHaveBeenCalledTimes(1); + expect(createObjectURLSpy).toHaveBeenCalledWith(blob); + + expect(fakeLink.download).toBe(fileName); + expect(fakeLink.style.display).toBe('none'); + expect(clickSpy).toHaveBeenCalledTimes(1); + }); +}); + +describe('toBlob', () => { + test('returns a blob', async () => { + const dotlottie = new DotLottie(); + + const blob = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + // const arrayBuffer = await blob.arrayBuffer(); + + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotlottie = new DotLottie(); + + const blob1 = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob({ + zipOptions: { + level: 9, + }, + }); + + const blob2 = await dotlottie.toBlob({ + zipOptions: { + level: 0, + }, + }); + + expect(blob1).toBeInstanceOf(Blob); + expect(blob2).toBeInstanceOf(Blob); + + const arrayBuffer1 = await blob1.arrayBuffer(); + const arrayBuffer2 = await blob2.arrayBuffer(); + + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toArrayBuffer', () => { + test('returns an array buffer', async () => { + const dotlottie = new DotLottie(); + + const arrayBuffer = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const arrayBuffer1 = await dotLottie1 + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer({ + zipOptions: { + level: 9, + }, + }); + + const arrayBuffer2 = await dotLottie1.toArrayBuffer({ + zipOptions: { + level: 0, + }, + }); + + expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toBase64', () => { + test('returns base64 string', async () => { + const dotlottie = new DotLottie(); + + const dataURL = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64(); + + const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; + const actualContent = unzipSync(new Uint8Array(actualArrayBuffer)); + + expect(Object.keys(actualContent).length).toBeGreaterThan(0); + expect(actualContent[`a/${manifest.animations[0]?.id}.json`]).toBeDefined(); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const dataURL1 = await dotLottie1 + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64({ + zipOptions: { + level: 9, + }, + }); + + const dataURL2 = await dotLottie1.toBase64({ + zipOptions: { + level: 0, + }, + }); + + const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; + const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; + + // expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); + }); +}); + +describe('fromURL', () => { + test('throws an error if the URL is invalid', async () => { + const dotLottie = new DotLottie(); + + await expect(dotLottie.fromURL('invalid-url')).rejects.toThrow('Invalid URL'); + }); + + test('loads a dotlottie file from a URL', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(dotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + const dotLottie = await new DotLottie().fromURL(animationURL); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotLottie.animations.length).toBe(1); + expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotLottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie1', + }, + ], + }); + + fetchSpy.mockRestore(); + }); + + test('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(editedDotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie01', + }, + ], + }); + + fetchSpy.mockRestore(); + }); +}); + +describe('fromArrayBuffer', () => { + test('loads a dotlottie file from an array buffer', async () => { + const arrayBuffer = dotlottieAnimation; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie1', + }, + ], + }); + }); + + test('loads a dotLottie containing images from an array buffer', async () => { + const arrayBuffer = bigMergedDotLottie; + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(6); + expect(dotlottie.getImages().length).toBe(16); + expect(dotlottie.animations[0]?.id).toEqual('v1'); + expect(dotlottie.animations[1]?.id).toEqual('v2'); + expect(dotlottie.animations[2]?.id).toEqual('v3'); + expect(dotlottie.animations[3]?.id).toEqual('v4'); + expect(dotlottie.animations[4]?.id).toEqual('v5'); + expect(dotlottie.animations[5]?.id).toEqual('v6'); + expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); + }); +}); + +describe('imageAssets', () => { + test('Adds the Bull animation and checks number of images.', async () => { + const dotlottie = await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(bullData) as unknown as AnimationData, + }) + .build(); + + const animation1 = await dotlottie.getAnimation('animation_1'); + + expect(animation1?.imageAssets.length).toBe(5); + }); +}); + +describe('merge', () => { + test('merges two dotlottie files', async () => { + const dotlottie1 = new DotLottie().addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }); + + const dotlottie2 = new DotLottie().addAnimation({ + id: 'lottie2', + data: animationData as unknown as AnimationType, + }); + + const dotlottie3 = new DotLottie().addAnimation({ + id: 'lottie3', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const dotlottie4 = new DotLottie().addAnimation({ + id: 'lottie4', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const shrekVariant1 = new DotLottie().addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }); + + const shrekVariant2 = new DotLottie().addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }); + + const shrekVariant3 = new DotLottie().addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }); + + const shrekVariant4 = new DotLottie().addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }); + + const shrekVariant5 = new DotLottie().addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }); + + const shrekVariant6 = new DotLottie().addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }); + + const [mergedImageLottie] = await Promise.all([ + new DotLottie().merge(dotlottie3, dotlottie4), + new DotLottie().merge(dotlottie3, dotlottie4).build(), + ]); + + const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ + new DotLottie() + .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) + .build(), + new DotLottie().merge(dotlottie1, dotlottie2).build(), + ]); + + expect(mergedImageLottie.animations.length).toBe(2); + + expect(bigMergedImageLottie.animations.length).toBe(6); + + expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ + 'v1', + 'v2', + 'v3', + 'v4', + 'v5', + 'v6', + ]); + + expect(mergedDotlottie).toBeInstanceOf(DotLottie); + + expect(mergedDotlottie.animations.length).toBe(2); + + expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); + expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); + expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); + }); +}); + +describe('build', () => { + test('it resolves lottie animations', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation.json'; + + const dotlottie = new DotLottie().addAnimation({ + url: animationURL, + id: 'lottie1', + }); + + expect(dotlottie.animations[0]?.data).toBeUndefined(); + + await dotlottie.build(); + + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + fetchSpy.mockRestore(); + }); +}); + +describe('theming', () => { + test('adds a global theme to the dotlottie file', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [ + { + id: 'ball-color', + type: 'Color', + value: [0, 1, 0, 1], + }, + ], + }, + }); + + await dotlottie.build(); + + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball' }], + themes: [{ id: 'light' }], + }); + + expect(dotlottie.animations[0]?.themes.length).toBe(0); + }); + + test('scopes a theme to an animation', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + dotlottie.scopeTheme({ + animationId: 'ball', + themeId: 'light', + }); + + await dotlottie.build(); + + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball', themes: ['light'] }], + themes: [{ id: 'light' }], + }); + + expect(dotlottie.animations[0]?.themes.length).toBe(1); + expect(dotlottie.animations[0]?.themes[0]?.id).toBe('light'); + }); + + test('throws an error if the theme does not exist', async () => { + const dotlottie = new DotLottie(); + + expect(() => dotlottie.scopeTheme({ animationId: 'ball', themeId: 'light' })).toThrow( + 'Failed to find theme with id light', + ); + }); + + test('throws an error if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + expect(() => dotlottie.scopeTheme({ animationId: 'ball', themeId: 'light' })).toThrow( + 'Failed to find animation with id ball', + ); + }); + + test('themes assets are properly exported in the .lottie file', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addAnimation({ + id: 'bull', + data: structuredClone(BULL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + name: 'Light Theme', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + dotlottie.addTheme({ + id: 'dark', + data: { + rules: [{ id: 'bull-color', type: 'Color', value: [1, 0, 0, 1] }], + }, + }); + + dotlottie.scopeTheme({ + animationId: 'bull', + themeId: 'dark', + }); + + await dotlottie.build(); + + const dotLottieFile = await dotlottie.toArrayBuffer(); + + const content = unzipSync(new Uint8Array(dotLottieFile)); + + expect(Object.keys(content)).toEqual([ + 'manifest.json', + 'a/ball.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + 't/light.json', + 't/dark.json', + ]); + + expect(strFromU8(content['t/light.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.themes[0]?.data)); + expect(strFromU8(content['a/ball.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.animations[0]?.data)); + expect(strFromU8(content['a/bull.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.animations[1]?.data)); + expect(strFromU8(content['manifest.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.manifest)); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball' }, { id: 'bull', themes: ['dark'] }], + themes: [{ id: 'light', name: 'Light Theme' }, { id: 'dark' }], + }); + }); +}); diff --git a/packages/dotlottie-js/src/tests/lottie-image-node.spec.ts b/packages/dotlottie-js/src/v2/__tests__/browser/image.spec.ts similarity index 75% rename from packages/dotlottie-js/src/tests/lottie-image-node.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/browser/image.spec.ts index 4517038e..fd59e980 100644 --- a/packages/dotlottie-js/src/tests/lottie-image-node.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/browser/image.spec.ts @@ -3,25 +3,27 @@ */ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; - -import { DotLottie, LottieImage, getMimeTypeFromBase64 } from '../node'; - -import BULL_DATA from './__fixtures__/image-asset-optimization/bull.json'; -import IMAGE_ANIMATION_1_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-1.json'; -import IMAGE_ANIMATION_5_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; -import IMAGE_ANIMATION_4_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; -import IMAGE_ANIMATION_3_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; -import IMAGE_ANIMATION_2_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2.json'; -import DUPES_DATA from './__fixtures__/image-asset-optimization/lots-of-dupes.json'; -import SIMPLE_IMAGE_ANIMATION from './__fixtures__/image-asset-optimization/simple-image-animation.json'; -import AUDIO_TEST from './__fixtures__/mimetype-tests/mp-3-test.txt'; -import SVG_XML_TEST from './__fixtures__/mimetype-tests/svg-xml-test.txt'; -import VIDEO_DOTLOTTIE from './__fixtures__/simple/video-embedded.lottie'; +import { describe, it, expect } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import DUPES_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import AUDIO_TEST from '../../../__tests__/__fixtures__/mimetype-tests/mp-3-test.txt?raw'; +import SVG_XML_TEST from '../../../__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt?raw'; +import VIDEO_DOTLOTTIE from '../../../__tests__/__fixtures__/simple/video-embedded.lottie?arraybuffer'; +import { getMimeTypeFromBase64 } from '../../../utils'; +import { DotLottie, LottieImage } from '../../index.browser'; describe('LottieImage', () => { it('gets and sets the zipOptions', () => { const theme = new LottieImage({ id: 'image_1', + lottieAssetId: 'image_1', fileName: 'image_1.png', zipOptions: { level: 9, @@ -70,7 +72,13 @@ describe('LottieImage', () => { 'image_3.png', 'image_4.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); }); }); @@ -110,7 +118,13 @@ describe('LottieImage', () => { ); expect(uniqueImages.length).toBe(5); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); expect(uniqueImages.map((image) => image.fileName)).toEqual([ 'image_0.png', 'image_1.png', @@ -122,29 +136,30 @@ describe('LottieImage', () => { }); it('Adds an animation with lots of duplicate images.', async () => { - const dotLottie = await new DotLottie({ enableDuplicateImageOptimization: true }) + await new DotLottie({ enableDuplicateImageOptimization: true }) .addAnimation({ id: 'animation_1', data: structuredClone(DUPES_DATA) as unknown as AnimationType, }) - .build(); - - const images = dotLottie.getImages(); + .build() + .then(async (value: DotLottie) => { + const images = value.getImages(); - // filter out unique images - const uniqueImages = images.filter( - (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, - ); + // filter out unique images + const uniqueImages = images.filter( + (image, index, self) => self.findIndex((compareImage) => compareImage.fileName === image.fileName) === index, + ); - expect(uniqueImages.length).toBe(4); + expect(uniqueImages.length).toBe(4); - expect(uniqueImages.map((image) => image.fileName)).toEqual([ - 'image_0.jpeg', - 'image_1.jpeg', - 'image_3.png', - 'image_4.png', - ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.fileName)).toEqual([ + 'image_0.jpeg', + 'image_1.jpeg', + 'image_3.png', + 'image_4.png', + ]); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); + }); }); it('Adds an animation with lots of duplicate images but disables image duplicate detection.', async () => { @@ -171,7 +186,13 @@ describe('LottieImage', () => { 'image_3.png', 'image_4.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); }); }); @@ -219,7 +240,7 @@ describe('LottieImage', () => { 'image_4.png', 'image_1_1.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual([ + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ 'image_0', 'image_1', 'image_3', diff --git a/packages/dotlottie-js/src/tests/lottie-state-browser.spec.ts b/packages/dotlottie-js/src/v2/__tests__/browser/state-machine.spec.ts similarity index 57% rename from packages/dotlottie-js/src/tests/lottie-state-browser.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/browser/state-machine.spec.ts index 9d2937f4..dabdc7b1 100644 --- a/packages/dotlottie-js/src/tests/lottie-state-browser.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/browser/state-machine.spec.ts @@ -4,24 +4,22 @@ /* eslint-disable no-new */ -import type { AnimationData } from '../common'; -import { DotLottie } from '../dotlottie'; -import { LottieStateMachine } from '../lottie-state-machine'; +import { describe, it, expect } from 'vitest'; -import animationData from './__fixtures__/simple/animation/animations/pigeon.json'; -import smileyAnimationData from './__fixtures__/simple/animation/animations/smiley.json'; -import wifiAnimationData from './__fixtures__/simple/animation/animations/wifi.json'; -import { SmileyWifi, PigeonState } from './__fixtures__/simple/state/pigeon-state'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/pigeon.json'; +import smileyAnimationData from '../../../__tests__/__fixtures__/simple/animation/animations/smiley.json'; +import wifiAnimationData from '../../../__tests__/__fixtures__/simple/animation/animations/wifi.json'; +import { SmileyWifi, PigeonState } from '../../../__tests__/__fixtures__/simple/state/pigeon-state'; +import type { AnimationData } from '../../../types'; +import { DotLottie, LottieStateMachine } from '../../index.browser'; -describe('LottieState', () => { +describe('LottieStateMachine', () => { it('throws an error if it receives an invalid id when constructed', () => { expect(() => { // act new LottieStateMachine({ - descriptor: { id: '', initial: 'pigeon' }, - states: PigeonState.states, - listeners: PigeonState.listeners ?? [], - triggers: PigeonState.triggers ?? [], + id: '', + data: PigeonState.data, }); // assert }).toThrowError('Invalid id.'); @@ -29,10 +27,7 @@ describe('LottieState', () => { it('gets and sets the zipOptions', () => { const theme = new LottieStateMachine({ - descriptor: PigeonState.descriptor, - states: PigeonState.states, - listeners: PigeonState.listeners ?? [], - triggers: PigeonState.triggers ?? [], + ...PigeonState, zipOptions: { level: 9, mem: 1, @@ -56,15 +51,18 @@ describe('LottieState', () => { it('gets and sets the id', () => { // arrange const state = new LottieStateMachine({ - descriptor: { id: 'test', initial: 'test' }, - states: [ - { - name: 'test', - type: 'PlaybackState', - mode: 'Forward', - autoplay: true, - }, - ], + id: 'test', + data: { + initial: 'test', + states: [ + { + name: 'test', + type: 'PlaybackState', + mode: 'Forward', + autoplay: true, + }, + ], + }, }); expect(state.id).toEqual('test'); @@ -81,11 +79,11 @@ describe('LottieState', () => { const pigeonState = new LottieStateMachine(PigeonState); // assert - expect(pigeonState.id).toEqual(PigeonState.descriptor.id); + expect(pigeonState.id).toEqual(PigeonState.id); - expect(pigeonState.initial).toEqual(PigeonState.descriptor.initial); + expect(pigeonState.initial).toEqual(PigeonState.data.initial); - expect(pigeonState.states).toEqual(PigeonState.states); + expect(pigeonState.states).toEqual(PigeonState.data.states); const dotlottie = new DotLottie(); @@ -109,24 +107,24 @@ describe('LottieState', () => { expect(dotlottie.stateMachines.length).toEqual(2); - expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.descriptor.id); + expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.id); - expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.descriptor.id); + expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.id); - expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.descriptor.id); - expect(dotlottie.stateMachines[0]?.initial).toEqual(PigeonState.descriptor.initial); - expect(dotlottie.stateMachines[0]?.states).toEqual(PigeonState.states); + expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.id); + expect(dotlottie.stateMachines[0]?.initial).toEqual(PigeonState.data.initial); + expect(dotlottie.stateMachines[0]?.states).toEqual(PigeonState.data.states); - expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.descriptor.id); - expect(dotlottie.stateMachines[1]?.initial).toEqual(SmileyWifi.descriptor.initial); - expect(dotlottie.stateMachines[1]?.states).toEqual(SmileyWifi.states); + expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.id); + expect(dotlottie.stateMachines[1]?.initial).toEqual(SmileyWifi.data.initial); + expect(dotlottie.stateMachines[1]?.states).toEqual(SmileyWifi.data.states); // Remove a state and check - dotlottie.removeStateMachine(PigeonState.descriptor.id); + dotlottie.removeStateMachine(PigeonState.id); expect(dotlottie.stateMachines.length).toEqual(1); - expect(dotlottie.stateMachines[0]?.id).toEqual(SmileyWifi.descriptor.id); + expect(dotlottie.stateMachines[0]?.id).toEqual(SmileyWifi.id); // dotlottie.download('test_02_with_states.lottie'); }); @@ -153,12 +151,12 @@ describe('LottieState', () => { await dotlottie.build(); - expect(dotlottie.manifest.states?.length).toEqual(2); + expect(dotlottie.manifest.stateMachines?.length).toEqual(2); - const values = [PigeonState.descriptor.id, SmileyWifi.descriptor.id]; + const values = [{ id: PigeonState.id }, { id: SmileyWifi.id }]; - if (dotlottie.manifest.states) { - dotlottie.manifest.states.forEach((value, index) => { + if (dotlottie.manifest.stateMachines) { + dotlottie.manifest.stateMachines.forEach((value, index) => { const val = values.at(index); if (val) return expect(value).toEqual(val); diff --git a/packages/dotlottie-js/src/v2/__tests__/node/animation.spec.ts b/packages/dotlottie-js/src/v2/__tests__/node/animation.spec.ts new file mode 100644 index 00000000..74424330 --- /dev/null +++ b/packages/dotlottie-js/src/v2/__tests__/node/animation.spec.ts @@ -0,0 +1,305 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable no-new */ +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import { LottieAnimation } from '../../index.node'; + +test('throws an error if it receives an invalid id when constructed', () => { + expect(() => { + new LottieAnimation({ id: '' }); + }).toThrow('Invalid animation id'); +}); + +test('throws an error if it receives an invalid url when constructed', () => { + const invalidUrl = 'xyz'; + + expect(() => { + new LottieAnimation({ id: 'test', url: invalidUrl }); + }).toThrow('Invalid animation url'); +}); + +test('throws an error if it receives an invalid lottie data when constructed', () => { + const invalidData = {} as AnimationType; + + expect(() => { + new LottieAnimation({ id: 'test', data: invalidData }); + }).toThrow('Received invalid Lottie data.'); +}); + +test('throws an error if it receives an no data or url when constructed', () => { + expect(() => { + new LottieAnimation({ id: 'test' }); + }).toThrow('No data or url provided.'); +}); + +test('gets and sets the zipOptions', () => { + const animation = new LottieAnimation({ + id: 'test', + data: animationData as unknown as AnimationType, + zipOptions: { + level: 9, + mem: 1, + }, + }); + + expect(animation.zipOptions).toEqual({ + level: 9, + mem: 1, + }); + + animation.zipOptions = { + level: 1, + }; + + expect(animation.zipOptions).toEqual({ + level: 1, + }); +}); + +test('gets and sets the id', () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.id).toEqual('test'); + + animation.id = 'test2'; + + expect(animation.id).toEqual('test2'); +}); + +test('gets and sets the data', () => { + const animation = new LottieAnimation({ id: 'test', url: 'https://example.com' }); + + expect(animation.data).toBeUndefined(); + + animation.data = animationData as unknown as AnimationType; + + expect(animation.data).toEqual(animationData as unknown as AnimationType); +}); + +test('gets and sets the url', () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + expect(animation.url).toBeUndefined(); + + animation.url = 'https://example.com'; + + expect(animation.url).toEqual('https://example.com'); +}); + +describe('toJSON', () => { + test('returns the animation data as a JSON object', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const jsonData = await animation.toJSON(); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + }); + + test('returns the animation with inlined data as a JSON object', async () => { + const animation = new LottieAnimation({ + id: 'test', + data: structuredClone(BULL_DATA) as unknown as unknown as AnimationType, + }); + + const jsonData = await animation.toJSON({ inlineAssets: true }); + + expect(jsonData).toEqual(BULL_DATA as unknown as unknown as AnimationType); + }); + + test('resolves the animation data from the provided url and returns the animation data as a JSON object', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const jsonData = await animation.toJSON(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(jsonData).toEqual(animationData as unknown as AnimationType); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toJSON()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBase64', () => { + test('returns the base64 of the animation', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const dataUrl = await animation.toBase64(); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + }); + + test('resolves the animation data from the provided url and returns the animation data as a data url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const dataUrl = await animation.toBase64(); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + expect(dataUrl).toEqual(Base64.toBase64(JSON.stringify(animationData))); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toBase64()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toBlob', () => { + test('returns the animation data as a blob', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as a blob', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const blob = await animation.toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const blobText = await blob.text(); + + expect(blobText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/e2dbfe51-c278-465e-a770-0a089bbdb050/invalid.json', + }); + + await expect(animation.toBlob()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); + +describe('toArrayBuffer', () => { + test('returns the animation data as an array buffer', async () => { + const animation = new LottieAnimation({ id: 'test', data: animationData as unknown as AnimationType }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + }); + + test('resolves the animation data from the provided url and returns the animation data as an array buffer', async () => { + const fetchSpy = vi.spyOn(global, 'fetch').mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation/test.json'; + + const animation = new LottieAnimation({ + id: 'test', + url: animationURL, + }); + + const arrayBuffer = await animation.toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + const arrayBufferText = new TextDecoder('utf-8').decode(arrayBuffer); + + expect(arrayBufferText).toEqual(JSON.stringify(animationData)); + + fetchSpy.mockRestore(); + }); + + test('throws an error if the animation data cannot be resolved from the provided url', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response('Invalid JSON', { status: 200 })); + + const animation = new LottieAnimation({ + id: 'test', + url: 'https://lottie.host/invalid.json', + }); + + await expect(animation.toArrayBuffer()).rejects.toThrow(/invalid json returned from url/iu); + + fetchSpy.mockRestore(); + }); +}); diff --git a/packages/dotlottie-js/src/tests/lottie-audio-node.spec.ts b/packages/dotlottie-js/src/v2/__tests__/node/audio.spec.ts similarity index 77% rename from packages/dotlottie-js/src/tests/lottie-audio-node.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/node/audio.spec.ts index a17bf1f9..e53e5b12 100644 --- a/packages/dotlottie-js/src/tests/lottie-audio-node.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/node/audio.spec.ts @@ -2,14 +2,15 @@ * Copyright 2023 Design Barn Inc. */ -import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +/* eslint-disable @lottiefiles/import-filename-format */ -import { DotLottie, LottieAudio, isAudioAsset } from '..'; +import type { Animation as AnimationType, Asset } from '@lottie-animation-community/lottie-types'; +import { describe, it, expect } from 'vitest'; -// eslint-disable-next-line @lottiefiles/import-filename-format -import AUDIO_ANIMATION_1_DATA from './__fixtures__/audio/instruments_1.json'; -// eslint-disable-next-line @lottiefiles/import-filename-format -import AUDIO_ANIMATION_2_DATA from './__fixtures__/audio/instruments_2.json'; +import AUDIO_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/audio/instruments_1.json'; +import AUDIO_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/audio/instruments_2.json'; +import { isAudioAsset } from '../../../utils'; +import { DotLottie, LottieAudio } from '../../index.node'; describe('LottieAudio', () => { it('gets and sets the zipOptions', () => { @@ -55,15 +56,15 @@ describe('LottieAudio', () => { const expectedData: string[] = []; // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_2_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); @@ -93,8 +94,8 @@ describe('LottieAudio', () => { const expectedData: string[] = []; // eslint-disable-next-line array-callback-return - structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset: any): void => { - if (isAudioAsset(asset)) { + structuredClone(AUDIO_ANIMATION_1_DATA).assets.map((asset): void => { + if (isAudioAsset(asset as Asset.Value)) { expectedData.push(asset.p); } }); diff --git a/packages/dotlottie-js/src/v2/__tests__/node/dotlottie.spec.ts b/packages/dotlottie-js/src/v2/__tests__/node/dotlottie.spec.ts new file mode 100644 index 00000000..2bb73cec --- /dev/null +++ b/packages/dotlottie-js/src/v2/__tests__/node/dotlottie.spec.ts @@ -0,0 +1,891 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +/* eslint-disable @lottiefiles/import-filename-format */ + +import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { strFromU8, unzipSync } from 'fflate'; +import { Base64 } from 'js-base64'; +import { describe, test, expect, vi } from 'vitest'; + +import pkg from '../../../../package.json'; +import BALL_ANIMATION_DATA from '../../../__tests__/__fixtures__/ball.json'; +import BULL_ANIMATION_DATA from '../../../__tests__/__fixtures__/bull.json'; +import bullData from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import dotlottieAnimation from '../../../__tests__/__fixtures__/simple/animation.lottie?arraybuffer'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/lottie1.json'; +import manifest from '../../../__tests__/__fixtures__/simple/animation/manifest.json'; +import bigMergedDotLottie from '../../../__tests__/__fixtures__/simple/big-merged-dotlottie.lottie?arraybuffer'; +import editedDotlottieAnimation from '../../../__tests__/__fixtures__/simple/edited-settings.lottie?arraybuffer'; +import editedAnimationData from '../../../__tests__/__fixtures__/simple/edited-settings/animations/lottie01.json'; +import editedManifest from '../../../__tests__/__fixtures__/simple/edited-settings/manifest.json'; +import type { AnimationData } from '../../../types'; +import type { AnimationOptions, ManifestAnimation } from '../../index.node'; +import { DotLottie, LottieAnimation } from '../../index.node'; + +describe('generator', () => { + test('has proper generator when no input provided', () => { + const dotLottie = new DotLottie(); + + expect(dotLottie.generator).toBe(`${pkg.name}@${pkg.version}`); + }); +}); + +describe('addAnimation', () => { + test('throws an error if it receives a duplicate id when constructed', () => { + expect(() => { + const dotLottie = new DotLottie(); + + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + dotLottie.addAnimation({ + id: 'test', + url: 'https://example.com/test.lottie', + }); + }).toThrow('Duplicate animation id detected, aborting.'); + }); + + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('adds an animation', () => { + const animationId = manifest.animations[0]?.id as string; + + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: animationId, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + const animation = dotlottie.animations[0]; + + expect(animation?.id).toBe(manifest.animations[0]?.id); + }); + + test('adds an animation using all customizable options', async () => { + const animationId = 'test_animation'; + + const dotlottie = new DotLottie(); + + const animationOptions: AnimationOptions = { + id: animationId, + data: animationData as unknown as AnimationType, + }; + + const manifestDataToCompare: ManifestAnimation = { + id: animationId, + }; + + dotlottie.addAnimation({ + ...animationOptions, + }); + + await dotlottie.build(); + + const animationManifest = dotlottie.manifest.animations[0]; + + expect(animationManifest).toEqual(manifestDataToCompare); + }); +}); + +describe('removeAnimation', () => { + test('returns the dotlottie instance', () => { + const dotlottie = new DotLottie(); + + const result = dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(result).toBe(dotlottie); + }); + + test('removes an animation', () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + expect(dotlottie.animations.length).toBe(1); + + dotlottie.removeAnimation(manifest.animations[0]?.id as string); + + expect(dotlottie.animations.length).toBe(0); + }); +}); + +describe('getAnimation', () => { + test('returns animation instance', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string); + + expect(animation).toBeInstanceOf(LottieAnimation); + + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(animationData as unknown as AnimationType); + }); + + test('returns undefined if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + const animation = await dotlottie.getAnimation('non_existent_animation'); + + expect(animation).toBeUndefined(); + }); + + test('returns animation instance with inlined assets', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: manifest.animations[0]?.id as string, + data: structuredClone(bullData) as unknown as AnimationData, + }); + + const animation = await dotlottie.getAnimation(manifest.animations[0]?.id as string, { inlineAssets: true }); + + expect(animation).toBeInstanceOf(LottieAnimation); + expect(animation?.id).toBe(manifest.animations[0]?.id); + expect(animation?.data).toEqual(bullData as unknown as AnimationData); + }); + + test('adds multiple animations and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: false }); + + dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA) as unknown as AnimationData, + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION) as unknown as AnimationData, + }); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); + + test('adds multiple animations, optimizes the images and verifies their inlined assets', async () => { + const dotlottie = new DotLottie({ enableDuplicateImageOptimization: true }); + + await dotlottie + .addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }) + .addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }) + .build(); + + const animationV1 = await dotlottie.getAnimation('v1', { inlineAssets: true }); + const animationV2 = await dotlottie.getAnimation('v2', { inlineAssets: true }); + const animationV3 = await dotlottie.getAnimation('v3', { inlineAssets: true }); + const animationV4 = await dotlottie.getAnimation('v4', { inlineAssets: true }); + const animationV5 = await dotlottie.getAnimation('v5', { inlineAssets: true }); + const animationV6 = await dotlottie.getAnimation('v6', { inlineAssets: true }); + + expect(animationV1).toBeInstanceOf(LottieAnimation); + expect(animationV1?.id).toBe('v1'); + expect(animationV1?.data).toEqual(IMAGE_ANIMATION_1_DATA as unknown as AnimationData); + + expect(animationV2).toBeInstanceOf(LottieAnimation); + expect(animationV2?.id).toBe('v2'); + expect(animationV2?.data).toEqual(IMAGE_ANIMATION_2_DATA as unknown as AnimationData); + + expect(animationV3).toBeInstanceOf(LottieAnimation); + expect(animationV3?.id).toBe('v3'); + expect(animationV3?.data).toEqual(IMAGE_ANIMATION_3_DATA as unknown as AnimationData); + + expect(animationV4).toBeInstanceOf(LottieAnimation); + expect(animationV4?.id).toBe('v4'); + expect(animationV4?.data).toEqual(IMAGE_ANIMATION_4_DATA as unknown as AnimationData); + + expect(animationV5).toBeInstanceOf(LottieAnimation); + expect(animationV5?.id).toBe('v5'); + expect(animationV5?.data).toEqual(IMAGE_ANIMATION_5_DATA as unknown as AnimationData); + + expect(animationV6).toBeInstanceOf(LottieAnimation); + expect(animationV6?.id).toBe('v6'); + expect(animationV6?.data).toEqual(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData); + }); +}); + +describe('download', () => { + test('throws error on node environment', async () => { + // skip test if running in browser environment + if (typeof window !== 'undefined') return; + + const dotlottie = new DotLottie(); + + expect( + dotlottie + .addAnimation({ + id: 'test_animation', + data: animationData as unknown as AnimationType, + }) + .download('file'), + ).rejects.toThrow('Cannot download dotlottie in a non-browser environment'); + }); + + test('downloads dotlottie file on browser', async () => { + // skip test if running in node environment + if (typeof window === 'undefined') return; + + const dotlottie = new DotLottie(); + + const fileName = 'test.lottie'; + + const fakeLink = document.createElement('a'); + + const clickSpy = vi.spyOn(fakeLink, 'click').mockImplementation(() => { + // do nothing + }); + + vi.spyOn(document, 'createElement').mockImplementation(() => { + return fakeLink; + }); + + const createObjectURLSpy = vi.spyOn(URL, 'createObjectURL'); + + await dotlottie + .addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }) + .build(); + + await dotlottie.download(fileName); + + const blob = await dotlottie.toBlob(); + + expect(createObjectURLSpy).toHaveBeenCalledTimes(1); + expect(createObjectURLSpy).toHaveBeenCalledWith(blob); + + expect(fakeLink.download).toBe(fileName); + expect(fakeLink.style.display).toBe('none'); + expect(clickSpy).toHaveBeenCalledTimes(1); + }); +}); + +describe('toBlob', () => { + test('returns a blob', async () => { + const dotlottie = new DotLottie(); + + const blob = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob(); + + expect(blob).toBeInstanceOf(Blob); + + // const arrayBuffer = await blob.arrayBuffer(); + + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotlottie = new DotLottie(); + + const blob1 = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBlob({ + zipOptions: { + level: 9, + }, + }); + + const blob2 = await dotlottie.toBlob({ + zipOptions: { + level: 0, + }, + }); + + expect(blob1).toBeInstanceOf(Blob); + expect(blob2).toBeInstanceOf(Blob); + + const arrayBuffer1 = await blob1.arrayBuffer(); + const arrayBuffer2 = await blob2.arrayBuffer(); + + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toArrayBuffer', () => { + test('returns an array buffer', async () => { + const dotlottie = new DotLottie(); + + const arrayBuffer = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer(); + + expect(arrayBuffer).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const arrayBuffer1 = await dotLottie1 + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toArrayBuffer({ + zipOptions: { + level: 9, + }, + }); + + const arrayBuffer2 = await dotLottie1.toArrayBuffer({ + zipOptions: { + level: 0, + }, + }); + + expect(arrayBuffer1).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer2).toBeInstanceOf(ArrayBuffer); + // expect(arrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(arrayBuffer1.byteLength).toBeLessThan(arrayBuffer2.byteLength); + }); +}); + +describe('toBase64', () => { + test('returns base64 string', async () => { + const dotlottie = new DotLottie(); + + const dataURL = await dotlottie + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64(); + + const actualArrayBuffer = Base64.toUint8Array(dataURL).buffer; + const actualContent = unzipSync(new Uint8Array(actualArrayBuffer)); + + expect(Object.keys(actualContent).length).toBeGreaterThan(0); + expect(actualContent[`a/${manifest.animations[0]?.id}.json`]).toBeDefined(); + }); + + test('Controls the compression level of the whole dotLottie file', async () => { + const dotLottie1 = new DotLottie(); + + const dataURL1 = await dotLottie1 + .addAnimation({ + id: manifest.animations[0]?.id as string, + data: animationData as unknown as AnimationType, + }) + .toBase64({ + zipOptions: { + level: 9, + }, + }); + + const dataURL2 = await dotLottie1.toBase64({ + zipOptions: { + level: 0, + }, + }); + + const actualArrayBuffer1 = Base64.toUint8Array(dataURL1).buffer; + const actualArrayBuffer2 = Base64.toUint8Array(dataURL2).buffer; + + // expect(actualArrayBuffer1).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + // expect(actualArrayBuffer2).toBeEqualDotlottieArrayBuffer(dotlottieAnimation); + + expect(actualArrayBuffer1.byteLength).toBeLessThan(actualArrayBuffer2.byteLength); + }); +}); + +describe('fromURL', () => { + test('throws an error if the URL is invalid', async () => { + const dotLottie = new DotLottie(); + + await expect(dotLottie.fromURL('invalid-url')).rejects.toThrow('Invalid URL'); + }); + + test('loads a dotlottie file from a URL', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(dotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + const dotLottie = await new DotLottie().fromURL(animationURL); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotLottie.animations.length).toBe(1); + expect(dotLottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotLottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotLottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie1', + }, + ], + }); + + fetchSpy.mockRestore(); + }); + + test('loads a dotLottie with non-default settings from a URL and verifies the animation settings', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(editedDotlottieAnimation)); + + const animationURL = 'https://lottiefiles.fake/animation/animation.lottie'; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromURL('https://lottiefiles.fake/animation/animation.lottie'); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(editedManifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(editedAnimationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie01', + }, + ], + }); + + fetchSpy.mockRestore(); + }); +}); + +describe('fromArrayBuffer', () => { + test('loads a dotlottie file from an array buffer', async () => { + const arrayBuffer = dotlottieAnimation; + + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(1); + expect(dotlottie.animations[0]?.id).toEqual(manifest.animations[0]?.id as string); + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [ + { + id: 'lottie1', + }, + ], + }); + }); + + test('loads a dotLottie containing images from an array buffer', async () => { + const arrayBuffer = bigMergedDotLottie; + let dotlottie = new DotLottie(); + + dotlottie = await dotlottie.fromArrayBuffer(arrayBuffer); + + expect(dotlottie.animations.length).toBe(6); + expect(dotlottie.getImages().length).toBe(16); + expect(dotlottie.animations[0]?.id).toEqual('v1'); + expect(dotlottie.animations[1]?.id).toEqual('v2'); + expect(dotlottie.animations[2]?.id).toEqual('v3'); + expect(dotlottie.animations[3]?.id).toEqual('v4'); + expect(dotlottie.animations[4]?.id).toEqual('v5'); + expect(dotlottie.animations[5]?.id).toEqual('v6'); + expect(dotlottie.animations.map((animation) => animation.id)).toEqual(['v1', 'v2', 'v3', 'v4', 'v5', 'v6']); + }); +}); + +describe('imageAssets', () => { + test('Adds the Bull animation and checks number of images.', async () => { + const dotlottie = await new DotLottie() + .addAnimation({ + id: 'animation_1', + data: structuredClone(bullData) as unknown as AnimationData, + }) + .build(); + + const animation1 = await dotlottie.getAnimation('animation_1'); + + expect(animation1?.imageAssets.length).toBe(5); + }); +}); + +describe('merge', () => { + test('merges two dotlottie files', async () => { + const dotlottie1 = new DotLottie().addAnimation({ + id: 'lottie1', + data: animationData as unknown as AnimationType, + }); + + const dotlottie2 = new DotLottie().addAnimation({ + id: 'lottie2', + data: animationData as unknown as AnimationType, + }); + + const dotlottie3 = new DotLottie().addAnimation({ + id: 'lottie3', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const dotlottie4 = new DotLottie().addAnimation({ + id: 'lottie4', + data: structuredClone(bullData as unknown as AnimationData), + }); + + const shrekVariant1 = new DotLottie().addAnimation({ + id: 'v1', + data: structuredClone(IMAGE_ANIMATION_1_DATA as unknown as AnimationData), + }); + + const shrekVariant2 = new DotLottie().addAnimation({ + id: 'v2', + data: structuredClone(IMAGE_ANIMATION_2_DATA as unknown as AnimationData), + }); + + const shrekVariant3 = new DotLottie().addAnimation({ + id: 'v3', + data: structuredClone(IMAGE_ANIMATION_3_DATA as unknown as AnimationData), + }); + + const shrekVariant4 = new DotLottie().addAnimation({ + id: 'v4', + data: structuredClone(IMAGE_ANIMATION_4_DATA as unknown as AnimationData), + }); + + const shrekVariant5 = new DotLottie().addAnimation({ + id: 'v5', + data: structuredClone(IMAGE_ANIMATION_5_DATA as unknown as AnimationData), + }); + + const shrekVariant6 = new DotLottie().addAnimation({ + id: 'v6', + data: structuredClone(SIMPLE_IMAGE_ANIMATION as unknown as AnimationData), + }); + + const [mergedImageLottie] = await Promise.all([ + new DotLottie().merge(dotlottie3, dotlottie4), + new DotLottie().merge(dotlottie3, dotlottie4).build(), + ]); + + const [bigMergedImageLottie, mergedDotlottie] = await Promise.all([ + new DotLottie() + .merge(shrekVariant1, shrekVariant2, shrekVariant3, shrekVariant4, shrekVariant5, shrekVariant6) + .build(), + new DotLottie().merge(dotlottie1, dotlottie2).build(), + ]); + + expect(mergedImageLottie.animations.length).toBe(2); + + expect(bigMergedImageLottie.animations.length).toBe(6); + + expect(bigMergedImageLottie.animations.map((animation) => animation.id)).toEqual([ + 'v1', + 'v2', + 'v3', + 'v4', + 'v5', + 'v6', + ]); + + expect(mergedDotlottie).toBeInstanceOf(DotLottie); + + expect(mergedDotlottie.animations.length).toBe(2); + + expect(mergedDotlottie.animations[0]?.id).toEqual('lottie1'); + expect(mergedDotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(mergedDotlottie.animations[1]?.id).toEqual('lottie2'); + expect(mergedDotlottie.animations[1]?.data).toEqual(animationData as unknown as AnimationType); + }); +}); + +describe('build', () => { + test('it resolves lottie animations', async () => { + const fetchSpy = vi + .spyOn(typeof window === 'undefined' ? global : window, 'fetch') + .mockResolvedValue(new Response(JSON.stringify(animationData))); + + const animationURL = 'https://lottiefiles.fake/animation.json'; + + const dotlottie = new DotLottie().addAnimation({ + url: animationURL, + id: 'lottie1', + }); + + expect(dotlottie.animations[0]?.data).toBeUndefined(); + + await dotlottie.build(); + + expect(dotlottie.animations[0]?.data).toEqual(animationData as unknown as AnimationType); + + expect(fetchSpy).toHaveBeenCalledTimes(1); + expect(fetchSpy).toHaveBeenCalledWith(animationURL); + + fetchSpy.mockRestore(); + }); +}); + +describe('theming', () => { + test('adds a global theme to the dotlottie file', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [ + { + id: 'ball-color', + type: 'Color', + value: [0, 1, 0, 1], + }, + ], + }, + }); + + await dotlottie.build(); + + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball' }], + themes: [{ id: 'light' }], + }); + + expect(dotlottie.animations[0]?.themes.length).toBe(0); + }); + + test('scopes a theme to an animation', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + dotlottie.scopeTheme({ + animationId: 'ball', + themeId: 'light', + }); + + await dotlottie.build(); + + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball', themes: ['light'] }], + themes: [{ id: 'light' }], + }); + + expect(dotlottie.animations[0]?.themes.length).toBe(1); + expect(dotlottie.animations[0]?.themes[0]?.id).toBe('light'); + }); + + test('throws an error if the theme does not exist', async () => { + const dotlottie = new DotLottie(); + + expect(() => dotlottie.scopeTheme({ animationId: 'ball', themeId: 'light' })).toThrow( + 'Failed to find theme with id light', + ); + }); + + test('throws an error if the animation does not exist', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addTheme({ + id: 'light', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + expect(() => dotlottie.scopeTheme({ animationId: 'ball', themeId: 'light' })).toThrow( + 'Failed to find animation with id ball', + ); + }); + + test('themes assets are properly exported in the .lottie file', async () => { + const dotlottie = new DotLottie(); + + dotlottie.addAnimation({ + id: 'ball', + data: structuredClone(BALL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addAnimation({ + id: 'bull', + data: structuredClone(BULL_ANIMATION_DATA) as unknown as AnimationData, + }); + + dotlottie.addTheme({ + id: 'light', + name: 'Light Theme', + data: { + rules: [{ id: 'ball-color', type: 'Color', value: [0, 1, 0, 1] }], + }, + }); + + dotlottie.addTheme({ + id: 'dark', + data: { + rules: [{ id: 'bull-color', type: 'Color', value: [1, 0, 0, 1] }], + }, + }); + + dotlottie.scopeTheme({ + animationId: 'bull', + themeId: 'dark', + }); + + await dotlottie.build(); + + const dotLottieFile = await dotlottie.toArrayBuffer(); + + const content = unzipSync(new Uint8Array(dotLottieFile)); + + expect(Object.keys(content)).toEqual([ + 'manifest.json', + 'a/ball.json', + 'a/bull.json', + 'i/image_0.png', + 'i/image_1.png', + 'i/image_2.png', + 'i/image_3.png', + 'i/image_4.png', + 't/light.json', + 't/dark.json', + ]); + + expect(strFromU8(content['t/light.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.themes[0]?.data)); + expect(strFromU8(content['a/ball.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.animations[0]?.data)); + expect(strFromU8(content['a/bull.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.animations[1]?.data)); + expect(strFromU8(content['manifest.json'] as Uint8Array)).toEqual(JSON.stringify(dotlottie.manifest)); + expect(dotlottie.manifest).toEqual({ + version: '2', + generator: `${pkg.name}@${pkg.version}`, + animations: [{ id: 'ball' }, { id: 'bull', themes: ['dark'] }], + themes: [{ id: 'light', name: 'Light Theme' }, { id: 'dark' }], + }); + }); +}); diff --git a/packages/dotlottie-js/src/tests/lottie-image-browser.spec.ts b/packages/dotlottie-js/src/v2/__tests__/node/image.spec.ts similarity index 80% rename from packages/dotlottie-js/src/tests/lottie-image-browser.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/node/image.spec.ts index ef2bfe25..ccbc6292 100644 --- a/packages/dotlottie-js/src/tests/lottie-image-browser.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/node/image.spec.ts @@ -3,25 +3,27 @@ */ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; - -import { DotLottie, LottieImage, getMimeTypeFromBase64 } from '..'; - -import BULL_DATA from './__fixtures__/image-asset-optimization/bull.json'; -import IMAGE_ANIMATION_1_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-1.json'; -import IMAGE_ANIMATION_5_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; -import IMAGE_ANIMATION_4_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; -import IMAGE_ANIMATION_3_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; -import IMAGE_ANIMATION_2_DATA from './__fixtures__/image-asset-optimization/image-animation-layer-2.json'; -import DUPES_DATA from './__fixtures__/image-asset-optimization/lots-of-dupes.json'; -import SIMPLE_IMAGE_ANIMATION from './__fixtures__/image-asset-optimization/simple-image-animation.json'; -import AUDIO_TEST from './__fixtures__/mimetype-tests/mp-3-test.txt'; -import SVG_XML_TEST from './__fixtures__/mimetype-tests/svg-xml-test.txt'; -import VIDEO_DOTLOTTIE from './__fixtures__/simple/video-embedded.lottie'; +import { describe, it, expect } from 'vitest'; + +import BULL_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/bull.json'; +import IMAGE_ANIMATION_1_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-1.json'; +import IMAGE_ANIMATION_5_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4-5.json'; +import IMAGE_ANIMATION_4_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3-4.json'; +import IMAGE_ANIMATION_3_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2-3.json'; +import IMAGE_ANIMATION_2_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/image-animation-layer-2.json'; +import DUPES_DATA from '../../../__tests__/__fixtures__/image-asset-optimization/lots-of-dupes.json'; +import SIMPLE_IMAGE_ANIMATION from '../../../__tests__/__fixtures__/image-asset-optimization/simple-image-animation.json'; +import AUDIO_TEST from '../../../__tests__/__fixtures__/mimetype-tests/mp-3-test.txt?raw'; +import SVG_XML_TEST from '../../../__tests__/__fixtures__/mimetype-tests/svg-xml-test.txt?raw'; +import VIDEO_DOTLOTTIE from '../../../__tests__/__fixtures__/simple/video-embedded.lottie?arraybuffer'; +import { getMimeTypeFromBase64 } from '../../../utils'; +import { DotLottie, LottieImage } from '../../index.node'; describe('LottieImage', () => { it('gets and sets the zipOptions', () => { const theme = new LottieImage({ id: 'image_1', + lottieAssetId: 'image_1', fileName: 'image_1.png', zipOptions: { level: 9, @@ -70,7 +72,13 @@ describe('LottieImage', () => { 'image_3.png', 'image_4.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); }); }); @@ -110,7 +118,13 @@ describe('LottieImage', () => { ); expect(uniqueImages.length).toBe(5); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); expect(uniqueImages.map((image) => image.fileName)).toEqual([ 'image_0.png', 'image_1.png', @@ -144,7 +158,7 @@ describe('LottieImage', () => { 'image_3.png', 'image_4.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual(['image_0', 'image_1', 'image_3', 'image_4']); }); }); @@ -172,7 +186,13 @@ describe('LottieImage', () => { 'image_3.png', 'image_4.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual(['image_0', 'image_1', 'image_2', 'image_3', 'image_4']); + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ + 'image_0', + 'image_1', + 'image_2', + 'image_3', + 'image_4', + ]); }); }); @@ -220,7 +240,7 @@ describe('LottieImage', () => { 'image_4.png', 'image_1_1.png', ]); - expect(uniqueImages.map((image) => image.id)).toEqual([ + expect(uniqueImages.map((image) => image.lottieAssetId)).toEqual([ 'image_0', 'image_1', 'image_3', diff --git a/packages/dotlottie-js/src/tests/lottie-state-node.spec.ts b/packages/dotlottie-js/src/v2/__tests__/node/state-machine.spec.ts similarity index 50% rename from packages/dotlottie-js/src/tests/lottie-state-node.spec.ts rename to packages/dotlottie-js/src/v2/__tests__/node/state-machine.spec.ts index f9d85ea9..51460d4b 100644 --- a/packages/dotlottie-js/src/tests/lottie-state-node.spec.ts +++ b/packages/dotlottie-js/src/v2/__tests__/node/state-machine.spec.ts @@ -4,24 +4,22 @@ /* eslint-disable no-new */ -import type { AnimationData } from '../common'; -import { DotLottie } from '../dotlottie'; -import { LottieStateMachine } from '../lottie-state-machine'; +import { describe, it, expect } from 'vitest'; -import animationData from './__fixtures__/simple/animation/animations/pigeon.json'; -import smileyAnimationData from './__fixtures__/simple/animation/animations/smiley.json'; -import wifiAnimationData from './__fixtures__/simple/animation/animations/wifi.json'; -import { SmileyWifi, PigeonState } from './__fixtures__/simple/state/pigeon-state'; +import animationData from '../../../__tests__/__fixtures__/simple/animation/animations/pigeon.json'; +import smileyAnimationData from '../../../__tests__/__fixtures__/simple/animation/animations/smiley.json'; +import wifiAnimationData from '../../../__tests__/__fixtures__/simple/animation/animations/wifi.json'; +import { SmileyWifi, PigeonState } from '../../../__tests__/__fixtures__/simple/state/pigeon-state'; +import type { AnimationData } from '../../../types'; +import { DotLottie, LottieStateMachine } from '../../index.node'; -describe('LottieState', () => { +describe('LottieStateMachine', () => { it('throws an error if it receives an invalid id when constructed', () => { expect(() => { // act new LottieStateMachine({ - descriptor: { id: '', initial: 'pigeon' }, - states: [], - listeners: [], - triggers: [], + id: '', + data: PigeonState.data, }); // assert }).toThrowError('Invalid id.'); @@ -29,10 +27,7 @@ describe('LottieState', () => { it('gets and sets the zipOptions', () => { const theme = new LottieStateMachine({ - descriptor: PigeonState.descriptor, - states: PigeonState.states, - listeners: PigeonState.listeners ?? [], - triggers: PigeonState.triggers ?? [], + ...PigeonState, zipOptions: { level: 9, mem: 1, @@ -56,15 +51,18 @@ describe('LottieState', () => { it('gets and sets the id', () => { // arrange const state = new LottieStateMachine({ - descriptor: { id: 'test', initial: 'test' }, - states: [ - { - name: 'test', - type: 'PlaybackState', - mode: 'Forward', - autoplay: true, - }, - ], + id: 'test', + data: { + initial: 'test', + states: [ + { + name: 'test', + type: 'PlaybackState', + mode: 'Forward', + autoplay: true, + }, + ], + }, }); expect(state.id).toEqual('test'); @@ -78,19 +76,14 @@ describe('LottieState', () => { it('gets and sets the data', async () => { // arrange - const pigeonState = new LottieStateMachine({ - descriptor: PigeonState.descriptor, - states: PigeonState.states, - listeners: PigeonState.listeners, - triggers: PigeonState.triggers, - }); + const pigeonState = new LottieStateMachine(PigeonState); // assert - expect(pigeonState.id).toEqual(PigeonState.descriptor.id); + expect(pigeonState.id).toEqual(PigeonState.id); - expect(pigeonState.initial).toEqual(PigeonState.descriptor.initial); + expect(pigeonState.initial).toEqual(PigeonState.data.initial); - expect(pigeonState.states).toEqual(PigeonState.states); + expect(pigeonState.states).toEqual(PigeonState.data.states); const dotlottie = new DotLottie(); @@ -114,24 +107,24 @@ describe('LottieState', () => { expect(dotlottie.stateMachines.length).toEqual(2); - expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.descriptor.id); + expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.id); - expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.descriptor.id); + expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.id); - expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.descriptor.id); - expect(dotlottie.stateMachines[0]?.initial).toEqual(PigeonState.descriptor.initial); - expect(dotlottie.stateMachines[0]?.states).toEqual(PigeonState.states); + expect(dotlottie.stateMachines[0]?.id).toEqual(PigeonState.id); + expect(dotlottie.stateMachines[0]?.initial).toEqual(PigeonState.data.initial); + expect(dotlottie.stateMachines[0]?.states).toEqual(PigeonState.data.states); - expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.descriptor.id); - expect(dotlottie.stateMachines[1]?.initial).toEqual(SmileyWifi.descriptor.initial); - expect(dotlottie.stateMachines[1]?.states).toEqual(SmileyWifi.states); + expect(dotlottie.stateMachines[1]?.id).toEqual(SmileyWifi.id); + expect(dotlottie.stateMachines[1]?.initial).toEqual(SmileyWifi.data.initial); + expect(dotlottie.stateMachines[1]?.states).toEqual(SmileyWifi.data.states); // Remove a state and check - dotlottie.removeStateMachine(PigeonState.descriptor.id); + dotlottie.removeStateMachine(PigeonState.id); expect(dotlottie.stateMachines.length).toEqual(1); - expect(dotlottie.stateMachines[0]?.id).toEqual(SmileyWifi.descriptor.id); + expect(dotlottie.stateMachines[0]?.id).toEqual(SmileyWifi.id); // dotlottie.download('test_02_with_states.lottie'); }); @@ -158,22 +151,18 @@ describe('LottieState', () => { await dotlottie.build(); - expect(dotlottie.manifest.states?.length).toEqual(2); - - // const values = [PigeonState.descriptor.id, SmileyWifi.descriptor.id]; + expect(dotlottie.manifest.stateMachines?.length).toEqual(2); - const expectedStates = [PigeonState.descriptor.id, SmileyWifi.descriptor.id]; + const values = [{ id: PigeonState.id }, { id: SmileyWifi.id }]; - expect(expectedStates).toEqual(dotlottie.manifest.states ?? []); + if (dotlottie.manifest.stateMachines) { + dotlottie.manifest.stateMachines.forEach((value, index) => { + const val = values.at(index); - // if (dotlottie.manifest.states) { - // dotlottie.manifest.states.forEach((value, index) => { - // const val = values.at(index); + if (val) return expect(value).toEqual(val); - // if (val) return expect(value).toEqual(val); - - // return false; - // }); - // } + return false; + }); + } }); }); diff --git a/packages/dotlottie-js/src/lottie-animation.ts b/packages/dotlottie-js/src/v2/browser/animation.ts similarity index 75% rename from packages/dotlottie-js/src/lottie-animation.ts rename to packages/dotlottie-js/src/v2/browser/animation.ts index da6f1843..c56149fe 100644 --- a/packages/dotlottie-js/src/lottie-animation.ts +++ b/packages/dotlottie-js/src/v2/browser/animation.ts @@ -4,10 +4,12 @@ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import type { AnimationOptions } from './common'; -import { DotLottieError, LottieAnimationCommon, createError, getExtensionTypeFromBase64, isAudioAsset } from './common'; -import { LottieImage } from './lottie-image'; -import { LottieAudio } from './node/lottie-audio'; +import { DotLottieError, getExtensionTypeFromBase64, isAudioAsset } from '../../utils'; +import type { AnimationOptions } from '../common'; +import { LottieAnimationCommon } from '../common'; + +import { LottieAudio } from './audio'; +import { LottieImage } from './image'; export class LottieAnimation extends LottieAnimationCommon { public constructor(options: AnimationOptions) { @@ -15,7 +17,7 @@ export class LottieAnimation extends LottieAnimationCommon { } /** - * Return the animation data as a base 64 encoded string. + * Return the animation data as a base64 encoded string. * * @returns data - The animation data as a base64 encoded string. * @throws Error - if the animation data is not set and the url is not provided. @@ -34,16 +36,17 @@ export class LottieAnimation extends LottieAnimationCommon { } /** - * Extract image assets from the anima tion. + * + * Extract image assets from the animation. * * @returns boolean - true on error otherwise false on success */ protected override async _extractImageAssets(): Promise { - if (!this._data) throw createError('Asset extraction failed.'); + if (!this._data) throw new DotLottieError('Failed to extract image assets: Animation data does not exist'); const animationAssets = this._data.assets as AnimationType['assets']; - if (!animationAssets) throw createError('Asset extraction failed.'); + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); for (const asset of animationAssets) { if ('w' in asset && 'h' in asset && !('xt' in asset) && 'p' in asset) { @@ -67,13 +70,14 @@ export class LottieAnimation extends LottieAnimationCommon { new LottieImage({ data: asset.p, id: asset.id, + lottieAssetId: asset.id, fileName, parentAnimations: [this], }), ); asset.p = fileName; - asset.u = '/images/'; + asset.u = '/i/'; asset.e = 0; } } @@ -89,17 +93,17 @@ export class LottieAnimation extends LottieAnimationCommon { * @returns boolean - true on error otherwise false on success */ protected override async _extractAudioAssets(): Promise { - if (!this._data) throw new DotLottieError('Asset extraction failed.'); + if (!this._data) throw new DotLottieError('Failed to extract audio assets: Animation data does not exist'); const animationAssets = this._data.assets as AnimationType['assets']; - if (!animationAssets) throw new DotLottieError('Asset extraction failed.'); + if (!animationAssets) throw new DotLottieError('Failed to extract image assets: No assets found inside animation'); for (const asset of animationAssets) { if (isAudioAsset(asset)) { const audioData = asset.p.split(','); - // Image data is invalid + // Audio data is invalid if (!audioData.length || !audioData[0] || !audioData[1]) { break; } @@ -121,7 +125,7 @@ export class LottieAnimation extends LottieAnimationCommon { ); asset.p = fileName; - asset.u = '/audio/'; + asset.u = '/u/'; asset.e = 0; } } diff --git a/packages/dotlottie-js/src/node/lottie-audio.ts b/packages/dotlottie-js/src/v2/browser/audio.ts similarity index 100% rename from packages/dotlottie-js/src/node/lottie-audio.ts rename to packages/dotlottie-js/src/v2/browser/audio.ts diff --git a/packages/dotlottie-js/src/dotlottie.ts b/packages/dotlottie-js/src/v2/browser/dotlottie.ts similarity index 62% rename from packages/dotlottie-js/src/dotlottie.ts rename to packages/dotlottie-js/src/v2/browser/dotlottie.ts index a62bdf5f..d6570ec1 100644 --- a/packages/dotlottie-js/src/dotlottie.ts +++ b/packages/dotlottie-js/src/v2/browser/dotlottie.ts @@ -2,53 +2,76 @@ * Copyright 2023 Design Barn Inc. */ +/* eslint-disable @typescript-eslint/no-use-before-define */ + import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; import type { Zippable } from 'fflate'; import { strToU8, zip, strFromU8, unzip } from 'fflate'; -import type { - DotLottiePlugin, - AnimationOptions, - DotLottieOptions, - ManifestAnimation, - Manifest, - ConversionOptions, -} from './common'; +import type { ConversionOptions } from '../../types'; import { - DotLottieCommon, - createError, base64ToUint8Array, - getExtensionTypeFromBase64, DotLottieError, + getDotLottieVersion, + getExtensionTypeFromBase64, isAudioAsset, -} from './common'; -import { DuplicateImageDetector } from './duplicate-image-detector'; -import { LottieAnimation } from './lottie-animation'; -import { LottieImage } from './lottie-image'; -import { LottieAudio } from './node/lottie-audio'; +} from '../../utils'; +import { DotLottieV1 } from '../../v1/browser'; +import type { AnimationOptions, DotLottieOptions } from '../common'; +import { DotLottieCommon } from '../common'; +import type { Manifest } from '../common/schemas'; + +import { LottieAnimation } from './animation'; +import { LottieAudio } from './audio'; +import { LottieImage } from './image'; +import { DuplicateImageDetector } from './plugins/duplicate-image-detector'; + +export async function toDotLottieV2(arrayBuffer: ArrayBuffer): Promise { + const version = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (version !== '2') { + const dotLottieV2 = new DotLottie(); + const dotLottieV1 = await new DotLottieV1().fromArrayBuffer(arrayBuffer); + + const animationIds = dotLottieV1.animations.map((animation) => animation.id); + + for (const animationId of animationIds) { + const animation = await dotLottieV1.getAnimation(animationId, { inlineAssets: true }); + + if (animation && animation.data) { + dotLottieV2.addAnimation({ + data: animation.data, + id: animationId, + }); + } + } + + await dotLottieV2.build(); + + return dotLottieV2; + } + + return new DotLottie().fromArrayBuffer(arrayBuffer); +} export class DotLottie extends DotLottieCommon { public constructor(options?: DotLottieOptions) { super(options); - if (this.enableDuplicateImageOptimization) this.addPlugins(new DuplicateImageDetector()); - } + if (this.enableDuplicateImageOptimization) { + const plugin = new DuplicateImageDetector(); - public override addPlugins(...plugins: DotLottiePlugin[]): DotLottieCommon { - plugins.forEach((plugin) => { plugin.install(this); this._plugins.push(plugin); - }); - - return this; + } } public override addAnimation(animationOptions: AnimationOptions): DotLottie { const animation = new LottieAnimation(animationOptions); if (this._animationsMap.get(animationOptions.id)) { - throw createError('Duplicate animation id detected, aborting.'); + throw new DotLottieError('Duplicate animation id detected, aborting.'); } this._animationsMap.set(animation.id, animation); @@ -56,7 +79,7 @@ export class DotLottie extends DotLottieCommon { return this; } - public override async toBase64(options: ConversionOptions | undefined): Promise { + public override async toBase64(options?: ConversionOptions): Promise { const data = await this.toArrayBuffer(options); const uint8Array = new Uint8Array(data); @@ -65,7 +88,7 @@ export class DotLottie extends DotLottieCommon { return window.btoa(binaryString); } - public override async download(fileName: string, options: ConversionOptions | undefined = undefined): Promise { + public override async download(fileName: string, options?: ConversionOptions): Promise { const blob = await this.toBlob(options); const dataURL = URL.createObjectURL(blob); @@ -92,23 +115,17 @@ export class DotLottie extends DotLottieCommon { return new DotLottie(options); } - public override async toArrayBuffer(options: ConversionOptions | undefined): Promise { + public override async toArrayBuffer(options?: ConversionOptions): Promise { const manifest = this._buildManifest(); const dotlottie: Zippable = { - 'manifest.json': [ - strToU8(JSON.stringify(manifest)), - { - // no compression for manifest - level: 0, - }, - ], + 'manifest.json': [strToU8(JSON.stringify(manifest)), {}], }; for (const animation of this.animations) { const json = await animation.toJSON(); - dotlottie[`animations/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; + dotlottie[`a/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; const imageAssets = animation.imageAssets; const audioAssets = animation.audioAssets; @@ -117,27 +134,27 @@ export class DotLottie extends DotLottieCommon { // Assure we have a base64 encoded version of the image const dataAsString = await asset.toDataURL(); - dotlottie[`images/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + dotlottie[`i/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; } for (const asset of audioAssets) { // Assure we have a base64 encoded version of the audio const dataAsString = await asset.toDataURL(); - dotlottie[`audio/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + dotlottie[`u/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; } } for (const theme of this.themes) { const themeData = await theme.toString(); - dotlottie[`themes/${theme.id}.json`] = [strToU8(themeData), theme.zipOptions]; + dotlottie[`t/${theme.id}.json`] = [strToU8(themeData), theme.zipOptions]; } for (const state of this.stateMachines) { const stateData = state.toString(); - dotlottie[`states/${state.id}.json`] = [strToU8(stateData), state.zipOptions]; + dotlottie[`s/${state.id}.json`] = [strToU8(stateData), state.zipOptions]; } const dotlottieArrayBuffer = await new Promise((resolve, reject) => { @@ -162,6 +179,12 @@ export class DotLottie extends DotLottieCommon { * @throws Error */ public override async fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise { + const dotLottieVersion = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (dotLottieVersion !== '2') { + return toDotLottieV2(arrayBuffer); + } + const dotlottie = new DotLottie(); try { @@ -183,65 +206,37 @@ export class DotLottie extends DotLottieCommon { try { // Parse the manifest first so that we can pick up animation settings const manifest = JSON.parse(strFromU8(contentObj['manifest.json'], false)) as Manifest; - const { author, custom, description, generator, keywords, version } = manifest; - - if (author) { - this._requireValidAuthor(author); - dotlottie.setAuthor(author); - } - if (custom) { - this._requireValidCustomData(custom); - dotlottie.setCustomData(custom); - } - if (description) { - this._requireValidDescription(description); - dotlottie.setDescription(description); - } - if (generator) { - this._requireValidGenerator(generator); - dotlottie.setGenerator(generator); - } - if (keywords) { - this._requireValidKeywords(keywords); - dotlottie.setKeywords(keywords); - } - if (version) { - this._requireValidVersion(version); - dotlottie.setVersion(version); - } for (const key of Object.keys(contentObj)) { // true is passed to use binary string, otherwise btoa fails const decodedStr = strFromU8(contentObj[key] as Uint8Array, true); - if (key.startsWith('animations/') && key.endsWith('.json')) { - // extract animationId from key as the key = `animations/${animationId}.json` - const animationId = /animations\/(.+)\.json/u.exec(key)?.[1]; + if (key.startsWith('a/') && key.endsWith('.json')) { + // extract animationId from key as the key = `a/${animationId}.json` + const animationId = /a\/(.+)\.json/u.exec(key)?.[1]; if (!animationId) { - throw createError('Invalid animation id'); + throw new DotLottieError('Invalid animation id'); } const animation = JSON.parse(decodedStr); - const animationSettings = manifest['animations'].find( - (anim: ManifestAnimation) => anim.id === animationId, - ); + const animationSettings = manifest.animations.find((anim) => anim.id === animationId); if (animationSettings === undefined) { - throw createError('Animation not found inside manifest'); + throw new DotLottieError('Animation not found inside manifest'); } dotlottie.addAnimation({ data: animation, ...animationSettings, }); - } else if (key.startsWith('images/')) { - // extract imageId from key as the key = `images/${imageId}.${ext}` - const imageId = /images\/(.+)\./u.exec(key)?.[1]; + } else if (key.startsWith('i/')) { + // extract imageId from key as the key = `i/${imageId}.${ext}` + const imageId = /i\/(.+)\./u.exec(key)?.[1]; if (!imageId) { - throw createError('Invalid image id'); + throw new DotLottieError('Invalid image id'); } let decodedImg = btoa(decodedStr); @@ -253,13 +248,14 @@ export class DotLottie extends DotLottieCommon { tmpImages.push( new LottieImage({ id: imageId, + lottieAssetId: imageId, data: decodedImg, fileName: key.split('/')[1] || '', }), ); - } else if (key.startsWith('audio/')) { - // extract audioId from key as the key = `audio/${audioId}.${ext}` - const audioId = /audio\/(.+)\./u.exec(key)?.[1]; + } else if (key.startsWith('u/')) { + // extract audioId from key as the key = `u/${audioId}.${ext}` + const audioId = /u\/(.+)\./u.exec(key)?.[1]; if (!audioId) { throw new DotLottieError('Invalid image id'); @@ -278,42 +274,38 @@ export class DotLottie extends DotLottieCommon { fileName: key.split('/')[1] || '', }), ); - } else if (key.startsWith('themes/') && key.endsWith('.json')) { - // extract themeId from key as the key = `themes/${themeId}.json` - const themeId = /themes\/(.+)\.json/u.exec(key)?.[1]; + } else if (key.startsWith('t/') && key.endsWith('.json')) { + // extract themeId from key as the key = `t/${themeId}.json` + const themeId = /t\/(.+)\.json/u.exec(key)?.[1]; if (!themeId) { - throw createError('Invalid theme id'); + throw new DotLottieError('Invalid theme id'); } manifest.themes?.forEach((theme) => { if (theme.id === themeId) { dotlottie.addTheme({ id: theme.id, + name: theme.name, data: JSON.parse(decodedStr), }); - - theme.animations.forEach((animationId) => { - dotlottie.assignTheme({ - animationId, - themeId, - }); - }); } }); - } else if (key.startsWith('states/') && key.endsWith('.json')) { - // extract stateId from key as the key = `states/${stateId}.json` - const stateId = /states\/(.+)\.json/u.exec(key)?.[1]; + } else if (key.startsWith('s/') && key.endsWith('.json')) { + // extract stateId from key as the key = `s/${stateId}.json` + const stateId = /s\/(.+)\.json/u.exec(key)?.[1]; if (!stateId) { - throw createError('Invalid theme id'); + throw new DotLottieError('Invalid theme id'); } - manifest.states?.forEach((state) => { - if (state === stateId) { - const decodedStateMachine = JSON.parse(decodedStr); - - dotlottie.addStateMachine(decodedStateMachine); + manifest.stateMachines?.forEach((stateMachine) => { + if (stateMachine.id === stateId) { + dotlottie.addStateMachine({ + id: stateMachine.id, + name: stateMachine.name, + data: JSON.parse(decodedStr), + }); } }); } diff --git a/packages/dotlottie-js/src/node/lottie-image.ts b/packages/dotlottie-js/src/v2/browser/image.ts similarity index 100% rename from packages/dotlottie-js/src/node/lottie-image.ts rename to packages/dotlottie-js/src/v2/browser/image.ts diff --git a/packages/dotlottie-js/src/v2/browser/index.ts b/packages/dotlottie-js/src/v2/browser/index.ts new file mode 100644 index 00000000..8501bc95 --- /dev/null +++ b/packages/dotlottie-js/src/v2/browser/index.ts @@ -0,0 +1,10 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './theme'; +export * from './state-machine'; +export * from './audio'; diff --git a/packages/dotlottie-js/src/duplicate-image-detector.ts b/packages/dotlottie-js/src/v2/browser/plugins/duplicate-image-detector.ts similarity index 82% rename from packages/dotlottie-js/src/duplicate-image-detector.ts rename to packages/dotlottie-js/src/v2/browser/plugins/duplicate-image-detector.ts index 0932c3cd..a0d6c6da 100644 --- a/packages/dotlottie-js/src/duplicate-image-detector.ts +++ b/packages/dotlottie-js/src/v2/browser/plugins/duplicate-image-detector.ts @@ -4,8 +4,8 @@ import { Hash, DifferenceHashBuilder } from 'browser-image-hash'; -import { DuplicateImageDetectorCommon } from './common'; -import type { LottieImageCommon } from './common'; +import type { LottieImageCommon } from '../../common'; +import { DuplicateImageDetectorCommon } from '../../common/plugins/duplicate-image-detector'; export class DuplicateImageDetector extends DuplicateImageDetectorCommon { public override async generatePhash(image: LottieImageCommon): Promise { diff --git a/packages/dotlottie-js/src/lottie-state-machine.ts b/packages/dotlottie-js/src/v2/browser/state-machine.ts similarity index 88% rename from packages/dotlottie-js/src/lottie-state-machine.ts rename to packages/dotlottie-js/src/v2/browser/state-machine.ts index f13545f4..4ef5322b 100644 --- a/packages/dotlottie-js/src/lottie-state-machine.ts +++ b/packages/dotlottie-js/src/v2/browser/state-machine.ts @@ -2,7 +2,7 @@ * Copyright 2023 Design Barn Inc. */ -import { DotLottieStateMachineCommon, type DotLottieStateMachineCommonOptions } from './common'; +import { DotLottieStateMachineCommon, type DotLottieStateMachineCommonOptions } from '../common'; export class LottieStateMachine extends DotLottieStateMachineCommon { public constructor(options: DotLottieStateMachineCommonOptions) { diff --git a/packages/dotlottie-js/src/node/lottie-theme.ts b/packages/dotlottie-js/src/v2/browser/theme.ts similarity index 100% rename from packages/dotlottie-js/src/node/lottie-theme.ts rename to packages/dotlottie-js/src/v2/browser/theme.ts diff --git a/packages/dotlottie-js/src/common/lottie-animation-common.ts b/packages/dotlottie-js/src/v2/common/animation.ts similarity index 64% rename from packages/dotlottie-js/src/common/lottie-animation-common.ts rename to packages/dotlottie-js/src/v2/common/animation.ts index a3e55565..67473ffc 100644 --- a/packages/dotlottie-js/src/common/lottie-animation-common.ts +++ b/packages/dotlottie-js/src/v2/common/animation.ts @@ -5,50 +5,32 @@ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; import type { ZipOptions } from 'fflate'; -import type { LottieThemeCommon } from './dotlottie-theme-common'; -import type { LottieAudioCommon } from './lottie-audio-common'; -import type { LottieImageCommon } from './lottie-image-common'; -import type { ManifestAnimation } from './manifest'; -import { PlayMode } from './manifest'; -import { DotLottieError, createError, isAudioAsset } from './utils'; +import type { AnimationData, ExportOptions } from '../../types'; +import { DotLottieError, isAudioAsset } from '../../utils'; -export type AnimationData = AnimationType; - -export interface ExportOptions { - inlineAssets?: boolean; -} +import type { LottieAudioCommon } from './audio'; +import type { LottieImageCommon } from './image'; +import type { ManifestAnimation } from './schemas'; +import type { LottieThemeCommon } from './theme'; export interface AnimationOptions extends ManifestAnimation { - data?: AnimationData; - defaultActiveAnimation?: boolean; - url?: string; - zipOptions?: ZipOptions; + data?: AnimationData | undefined; + defaultActiveAnimation?: boolean | undefined; + url?: string | undefined; + zipOptions?: ZipOptions | undefined; } export class LottieAnimationCommon { + protected _name: string | undefined; + protected _data?: AnimationData; protected _id: string = ''; protected _url?: string; - private _direction: ManifestAnimation['direction']; - - private _speed: number; - - private _playMode: PlayMode; - - private _loop: boolean | number; - - private _autoplay: boolean; - - private _hover: boolean; - - private _intermission: number; - private _zipOptions: ZipOptions; - // Will be translated to 'activeAnimationId' inside of the manifest file // This indicates if the player should play this animation by default rather than the first in the list. protected _defaultActiveAnimation: boolean; @@ -58,30 +40,28 @@ export class LottieAnimationCommon { protected _themesMap: Map = new Map(); - protected _defaultTheme?: string; + protected _initialTheme: string | null = null; + + protected _background: string | null = null; public constructor(options: AnimationOptions) { this._requireValidOptions(options); this._id = options.id; - + this._name = options.name; this._zipOptions = options.zipOptions ?? {}; if (options.data) this._data = options.data; if (options.url) this._url = options.url; - this._direction = options.direction ?? 1; - this._speed = options.speed ?? 1.0; - this._playMode = options.playMode ?? PlayMode.Normal; - this._loop = options.loop ?? false; - this._autoplay = options.autoplay ?? false; + this._background = options.background ?? null; + this._initialTheme = options.initialTheme ?? null; + this._defaultActiveAnimation = options.defaultActiveAnimation ?? false; - this._hover = options.hover ?? false; - this._intermission = options.intermission ?? 0; } public async toBase64(): Promise { - throw createError('lottie animation controls tobase64 not implemented!'); + throw new DotLottieError('lottie animation controls tobase64 not implemented!'); } public get zipOptions(): ZipOptions { @@ -102,14 +82,28 @@ export class LottieAnimationCommon { this._id = id; } - public get defaultTheme(): string | undefined { - return this._defaultTheme; + public get name(): string | undefined { + return this._name; } - public set defaultTheme(defaultTheme: string | undefined) { - if (defaultTheme) { - this._defaultTheme = defaultTheme; - } + public set name(name: string | undefined) { + this._name = name; + } + + public get background(): string | null { + return this._background; + } + + public set background(background: string | null) { + this._background = background; + } + + public get initialTheme(): string | null { + return this._initialTheme; + } + + public set initialTheme(initialTheme: string | null) { + this._initialTheme = initialTheme; } public get themes(): LottieThemeCommon[] { @@ -159,47 +153,6 @@ export class LottieAnimationCommon { this._url = url; } - public get direction(): ManifestAnimation['direction'] { - return this._direction; - } - - public set direction(direction: ManifestAnimation['direction']) { - this._direction = direction; - } - - public get speed(): number { - return this._speed; - } - - public set speed(speed: number) { - this._speed = speed; - } - - public get playMode(): PlayMode { - return this._playMode; - } - - public set playMode(playMode: PlayMode) { - this._playMode = playMode; - } - - public get loop(): boolean | number { - return this._loop; - } - - public set loop(loop: boolean | number) { - this._requireValidLoop(loop); - this._loop = loop; - } - - public get autoplay(): boolean { - return this._autoplay; - } - - public set autoplay(autoplay: boolean) { - this._autoplay = autoplay; - } - public get defaultActiveAnimation(): boolean { return this._defaultActiveAnimation; } @@ -208,28 +161,11 @@ export class LottieAnimationCommon { this._defaultActiveAnimation = defaultActiveAnimation; } - public get hover(): boolean { - return this._hover; - } - - public set hover(hover: boolean) { - this._hover = hover; - } - - public get intermission(): number { - return this._intermission; - } - - public set intermission(intermission: number) { - this._requireValidIntermission(intermission); - this._intermission = intermission; - } - - public addTheme(theme: LottieThemeCommon): void { + public scopeTheme(theme: LottieThemeCommon): void { this._themesMap.set(theme.id, theme); } - public removeTheme(themeId: string): void { + public unscopeTheme(themeId: string): void { this._themesMap.delete(themeId); } @@ -240,7 +176,7 @@ export class LottieAnimationCommon { * @throws Error - if the animation data is not a valid Lottie animation data object. * @throws Error - if the fetch request fails. */ - public async toArrayBuffer(options: ExportOptions = {}): Promise { + public async toArrayBuffer(options?: ExportOptions): Promise { const dataJson = await this.toJSON(options); return new TextEncoder().encode(JSON.stringify(dataJson)).buffer; @@ -261,7 +197,7 @@ export class LottieAnimationCommon { * @throws Error - if the animation data is not a valid Lottie animation data object. * @throws Error - if the fetch request fails. */ - public async toBlob(options: ExportOptions = {}): Promise { + public async toBlob(options?: ExportOptions): Promise { const dataJson = await this.toJSON(options); return new Blob([JSON.stringify(dataJson)], { type: 'application/json' }); @@ -274,7 +210,7 @@ export class LottieAnimationCommon { * @throws Error - if the animation data is not a valid Lottie animation data object. * @throws Error - if the fetch request fails. */ - public async toJSON(options: ExportOptions = {}): Promise { + public async toJSON(options?: ExportOptions): Promise { if (this._url && !this._data) { this._data = await this._fromUrl(this._url); } @@ -286,7 +222,7 @@ export class LottieAnimationCommon { await this._extractImageAssets(); await this._extractAudioAssets(); - if (options.inlineAssets) { + if (options?.inlineAssets) { const animationAssets = this.data?.assets as AnimationType['assets']; if (!animationAssets) @@ -341,7 +277,7 @@ export class LottieAnimationCommon { json = JSON.parse(text); } catch (error) { if (error instanceof Error) { - throw createError(`${error.message}: Invalid json returned from url`); + throw new DotLottieError(`${error.message}: Invalid json returned from url`); } } @@ -362,7 +298,7 @@ export class LottieAnimationCommon { // eslint-disable-next-line no-new new URL(url || ''); } catch (_err) { - throw createError('Invalid animation url'); + throw new DotLottieError('Invalid animation url'); } } @@ -381,7 +317,7 @@ export class LottieAnimationCommon { ); if (!hasAllMandatoryProperties) { - throw createError('Received invalid Lottie data.'); + throw new DotLottieError('Received invalid Lottie data.'); } } @@ -392,42 +328,7 @@ export class LottieAnimationCommon { * @throws Error - if the id is not a valid string. */ private _requireValidId(id: string | undefined): asserts id is string { - if (!id) throw createError('Invalid animation id'); - } - - /** - * Ensure that the provided url is a valid string. - * The url must be a non-empty string, otherwise an error will be thrown. - * @param url - The url to validate. - * @throws Error - if the url is not a valid string. - * - */ - private _requireValidDirection(direction: number): asserts direction is number { - if (direction !== -1 && direction !== 1) { - throw createError('Direction can only be -1 (backwards) or 1 (forwards)'); - } - } - - /** - * Ensure that the provided intermission is a valid, positive number. - * @param intermission - The intermission to validate. - * @throws Error - if the intermission is not a valid number. - */ - private _requireValidIntermission(intermission: number): asserts intermission is number { - if (intermission < 0 || !Number.isInteger(intermission)) { - throw createError('intermission must be a positive number'); - } - } - - /** - * Ensure that the provided loop is a valid, positive number or boolean. - * @param loop - The loop to validate. - * @throws Error - if the loop is not a valid number or boolean. - */ - private _requireValidLoop(loop: number | boolean): asserts loop is number | boolean { - if (typeof loop === 'number' && (!Number.isInteger(loop) || loop < 0)) { - throw createError('loop must be a positive number or boolean'); - } + if (!id) throw new DotLottieError('Invalid animation id'); } /** @@ -445,7 +346,7 @@ export class LottieAnimationCommon { this._requireValidId(options.id); if (!options.data && !options.url) { - throw createError('No data or url provided.'); + throw new DotLottieError('No data or url provided.'); } if (options.data) { @@ -455,17 +356,5 @@ export class LottieAnimationCommon { if (options.url) { this._requireValidUrl(options.url); } - - if (options.direction) { - this._requireValidDirection(options.direction); - } - - if (options.intermission) { - this._requireValidIntermission(options.intermission); - } - - if (options.loop) { - this._requireValidLoop(options.loop); - } } } diff --git a/packages/dotlottie-js/src/common/lottie-audio-common.ts b/packages/dotlottie-js/src/v2/common/audio.ts similarity index 90% rename from packages/dotlottie-js/src/common/lottie-audio-common.ts rename to packages/dotlottie-js/src/v2/common/audio.ts index 2a235f94..0c5b5e24 100644 --- a/packages/dotlottie-js/src/common/lottie-audio-common.ts +++ b/packages/dotlottie-js/src/v2/common/audio.ts @@ -4,10 +4,10 @@ import type { ZipOptions } from 'fflate'; -import type { LottieAnimationCommon } from './lottie-animation-common'; -import { dataUrlFromU8, DotLottieError, ErrorCodes } from './utils'; +import type { AudioData } from '../../types'; +import { dataUrlFromU8, DotLottieError, ErrorCodes, getExtensionTypeFromBase64 } from '../../utils'; -export type AudioData = string | ArrayBuffer | Blob; +import type { LottieAnimationCommon } from './animation'; export interface AudioOptions { data?: AudioData; @@ -114,18 +114,18 @@ export class LottieAudioCommon { * Renames the id and fileName to newName. * @param newName - A new id and filename for the audio. */ - public renameAudio(newName: string): void { + public async renameAudio(newName: string): Promise { this.id = newName; - if (this.fileName) { - let fileExt = this.fileName.split('.').pop(); + const data = await this.toDataURL(); - if (!fileExt) { - fileExt = '.png'; - } - // Default to png if the file extension isn't available - this.fileName = `${newName}.${fileExt}`; + const ext = await getExtensionTypeFromBase64(data); + + if (!ext) { + throw new DotLottieError('File extension type could not be detected from asset file.'); } + + this.fileName = `${newName}.${ext}`; } public async toArrayBuffer(): Promise { diff --git a/packages/dotlottie-js/src/common/dotlottie-common.ts b/packages/dotlottie-js/src/v2/common/dotlottie.ts similarity index 60% rename from packages/dotlottie-js/src/common/dotlottie-common.ts rename to packages/dotlottie-js/src/v2/common/dotlottie.ts index e7b2f9c8..4269ca69 100644 --- a/packages/dotlottie-js/src/common/dotlottie-common.ts +++ b/packages/dotlottie-js/src/v2/common/dotlottie.ts @@ -3,39 +3,24 @@ */ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import type { ZipOptions } from 'fflate'; -import pkg from '../../package.json'; +import { PACKAGE_NAME } from '../../constants'; +import type { ConversionOptions, GetAnimationOptions } from '../../types'; +import { DotLottieError, isAudioAsset, isImageAsset, isValidURL } from '../../utils'; -import type { DotLottiePlugin } from './dotlottie-plugin'; -import type { DotLottieStateMachineCommonOptions } from './dotlottie-state-machine-common'; -import { DotLottieStateMachineCommon } from './dotlottie-state-machine-common'; -import type { ThemeOptions } from './dotlottie-theme-common'; -import { LottieThemeCommon } from './dotlottie-theme-common'; -import type { AnimationOptions, LottieAnimationCommon } from './lottie-animation-common'; -import type { LottieAudioCommon } from './lottie-audio-common'; -import type { LottieImageCommon } from './lottie-image-common'; -import type { Manifest } from './manifest'; -import { DotLottieError, createError, isAudioAsset, isImageAsset, isValidURL } from './utils'; +import type { AnimationOptions, LottieAnimationCommon } from './animation'; +import type { LottieAudioCommon } from './audio'; +import type { LottieImageCommon } from './image'; +import type { DotLottiePlugin } from './plugin'; +import type { Manifest } from './schemas'; +import type { DotLottieStateMachineCommonOptions } from './state-machine'; +import { DotLottieStateMachineCommon } from './state-machine'; +import type { ThemeOptions } from './theme'; +import { LottieThemeCommon } from './theme'; export interface DotLottieOptions { - author?: string; - customData?: Record; - description?: string; enableDuplicateImageOptimization?: boolean; generator?: string; - keywords?: string; - plugins?: DotLottiePlugin[]; - revision?: number; - version?: string; -} - -export interface GetAnimationOptions { - inlineAssets?: boolean; -} - -export interface ConversionOptions { - zipOptions?: ZipOptions; } export class DotLottieCommon { @@ -47,96 +32,59 @@ export class DotLottieCommon { protected readonly _stateMachinesMap: Map = new Map(); - protected _author?: string; - - protected _description?: string; - - protected _generator?: string; + protected _generator: string = PACKAGE_NAME; - protected _keywords?: string; - - protected _version?: string; - - protected _revision?: number; - - // Custom data for the dotLottie - protected _customData?: Record; + protected _version: string = '2'; public enableDuplicateImageOptimization?: boolean; public constructor(options?: DotLottieOptions) { - this._author = options?.author ?? 'LottieFiles'; - - this._description = options?.description ?? ''; - - this._generator = options?.generator ?? `${pkg.name}@${pkg.version}`; - - this._keywords = options?.keywords ?? 'dotLottie'; - - this._version = options?.version ?? '1.0'; - - this._customData = options?.customData ?? {}; - - this._revision = options?.revision ?? 1; + if (options?.generator) { + this._generator = options.generator; + } this.enableDuplicateImageOptimization = options?.enableDuplicateImageOptimization ?? false; } - public async toBase64(_options: ConversionOptions | undefined = undefined): Promise { - throw createError('toBase64() method not implemented in concrete class!'); + public async toBase64(_options?: ConversionOptions): Promise { + throw new DotLottieError('toBase64() method not implemented in concrete class!'); } public create(_options?: DotLottieOptions): DotLottieCommon { - throw createError('create() method not implemented in concrete class!'); + throw new DotLottieError('create() method not implemented in concrete class!'); } public async download(_fileName: string, _options: ConversionOptions | undefined = undefined): Promise { - throw createError('download(fileName:string) method not implemented in concrete class!'); + throw new DotLottieError('download(fileName:string) method not implemented in concrete class!'); } public addPlugins(..._plugins: DotLottiePlugin[]): DotLottieCommon { - throw createError('addPlugins(...plugins: DotLottiePlugin[]) not implemented in concrete class!'); + throw new DotLottieError('addPlugins(...plugins: DotLottiePlugin[]) not implemented in concrete class!'); } public addAnimation(_animationOptions: AnimationOptions): DotLottieCommon { - throw createError('addAnimation(animationOptions: AnimationOptions) not implemented in concrete class!'); + throw new DotLottieError('addAnimation(animationOptions: AnimationOptions) not implemented in concrete class!'); } public async fromArrayBuffer(_arrayBuffer: ArrayBuffer): Promise { - throw createError( + throw new DotLottieError( 'fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise not implemented in concrete class!', ); } public async toArrayBuffer(_options: ConversionOptions | undefined = undefined): Promise { - throw createError('toArrayBuffer(): Promise is not implemented in concrete class!'); + throw new DotLottieError('toArrayBuffer(): Promise is not implemented in concrete class!'); } public get plugins(): DotLottiePlugin[] { return this._plugins; } - public get version(): string | undefined { + public get version(): string { return this._version; } - public get revision(): number | undefined { - return this._revision; - } - - public get author(): string | undefined { - return this._author; - } - - public get description(): string | undefined { - return this._description; - } - - public get keywords(): string | undefined { - return this._keywords; - } - - public get generator(): string | undefined { + public get generator(): string { return this._generator; } @@ -148,10 +96,6 @@ export class DotLottieCommon { return this._buildManifest(); } - public get custom(): Record | undefined { - return this._customData; - } - public get themes(): LottieThemeCommon[] { return Array.from(this._themesMap.values()); } @@ -160,79 +104,28 @@ export class DotLottieCommon { return Array.from(this._stateMachinesMap.values()); } - public setCustomData(customData: Record | undefined): DotLottieCommon { - this._customData = customData ?? {}; - - return this; - } - - public setAuthor(author: string | undefined): DotLottieCommon { - this._author = typeof author === 'string' ? author : 'LottieFiles'; - - return this; - } - - public setDescription(description: string | undefined): DotLottieCommon { - this._description = typeof description === 'string' ? description : ''; - - return this; - } - - public setGenerator(generator: string | undefined): DotLottieCommon { - this._generator = typeof generator === 'string' ? generator : `${pkg.name}@${pkg.version}`; - - return this; - } - - public setKeywords(keywords: string | undefined): DotLottieCommon { - this._keywords = typeof keywords === 'string' ? keywords : 'dotLottie'; - - return this; - } - - public setVersion(version: string | undefined): DotLottieCommon { - this._version = typeof version === 'string' ? version : '1.0'; - - return this; - } - - public setRevision(revision: number): DotLottieCommon { - this._revision = revision; - - return this; - } - - public removePlugins(...plugins: DotLottiePlugin[]): DotLottieCommon { - plugins.forEach((plugin) => { - plugin.uninstall(); - - const pluginIndex = this._plugins.indexOf(plugin); - - if (pluginIndex !== -1) { - this._plugins.splice(pluginIndex, 1); - } - }); - - return this; - } - /** * Renames the underlying LottieImage, as well as updating the image asset path inside the animation data. * @param newName - desired id and fileName, * @param imageId - The id of the LottieImage to rename */ - private async _renameImage(animation: LottieAnimationCommon, newName: string, imageId: string): Promise { + private async _renameImage( + animation: LottieAnimationCommon, + newLottieAssetId: string, + lottieAssetId: string, + ): Promise { for (const imageAsset of animation.imageAssets) { - if (imageAsset.id === imageId) { + if (imageAsset.lottieAssetId === lottieAssetId) { const oldPath = imageAsset.fileName; - await imageAsset.renameImage(newName); + // Rename image will change the fileName using the newLottieAssetId and append the detected extension + await imageAsset.renameImage(newLottieAssetId); - if (!animation.data) throw createError('No animation data available.'); + if (!animation.data) throw new DotLottieError('No animation data available.'); const animationAssets = animation.data.assets as AnimationType['assets']; - if (!animationAssets) throw createError('No image assets to rename.'); + if (!animationAssets) throw new DotLottieError('No image assets to rename.'); // Find the image asset inside the animation data and rename its path for (const asset of animationAssets) { @@ -255,12 +148,12 @@ export class DotLottieCommon { this.animations.forEach((animation) => { animation.imageAssets.forEach((imageAsset) => { - if (dupeMap.has(imageAsset.id)) { - const count = dupeMap.get(imageAsset.id) ?? 0; + if (dupeMap.has(imageAsset.lottieAssetId)) { + const count = dupeMap.get(imageAsset.lottieAssetId) ?? 0; - dupeMap.set(imageAsset.id, count + 1); + dupeMap.set(imageAsset.lottieAssetId, count + 1); } else { - dupeMap.set(imageAsset.id, 1); + dupeMap.set(imageAsset.lottieAssetId, 1); } }); }); @@ -270,29 +163,47 @@ export class DotLottieCommon { /** * Renames the image assets in all animations to avoid conflicts. + * + * Steps: + * - Generate how many times across all animations the same image id has been used. + * - Loop through every animation in reverse order + * - Every time an animation uses an image asset that is also used elsewhere, append the count to the image's asset id and then decrement. + * + * Result of renaming for every animation: + * + * - Inside the Lottie's data and it's Asset object: + * - The Asset id stays the same, meaning that every reference to the asset is still valid (refId) + * - The path is changed to the new asset id with the format \{assetId\}_\{count\} + * + * - On the dotLottie file system scope: + * - The image file name is changed to the new asset id \{assetId\}_\{count\}.\{ext\} */ private async _renameImageAssets(): Promise { - const dupeMap = this._generateMapOfOccurencesFromImageIds(); + const occurenceMap = this._generateMapOfOccurencesFromImageIds(); + // Loop over every animation for (let i = this.animations.length - 1; i >= 0; i -= 1) { const animation = this.animations.at(i); if (animation) { + // Loop over every image asset of the animation for (let j = animation.imageAssets.length - 1; j >= 0; j -= 1) { const image = animation.imageAssets.at(j); if (image) { - let count = dupeMap.get(image.id) ?? 0; + // Get how many times the same image id has been used + let count = occurenceMap.get(image.lottieAssetId) ?? 0; if (count > 0) { count -= 1; } - dupeMap.set(image.id, count); + // Decrement the count + occurenceMap.set(image.lottieAssetId, count); if (count > 0) { - // Rename the image - await this._renameImage(animation, `${image.id}_${count}`, image.id); + // Rename the with n-1 count + await this._renameImage(animation, `${image.lottieAssetId}_${count}`, image.lottieAssetId); } } } @@ -305,11 +216,11 @@ export class DotLottieCommon { * @param newName - desired id and fileName, * @param audioId - The id of the LottieAudio to rename */ - private _renameAudio(animation: LottieAnimationCommon, newName: string, audioId: string): void { - animation.audioAssets.forEach((audioAsset) => { + private async _renameAudio(animation: LottieAnimationCommon, newName: string, audioId: string): Promise { + for (const audioAsset of animation.audioAssets) { if (audioAsset.id === audioId) { // Rename the LottieImage - audioAsset.renameAudio(newName); + await audioAsset.renameAudio(newName); if (!animation.data) throw new DotLottieError('No animation data available.'); @@ -326,10 +237,10 @@ export class DotLottieCommon { } } } - }); + } } - private _renameAudioAssets(): void { + private async _renameAudioAssets(): Promise { const audio: Map = new Map(); this.animations.forEach((animation) => { @@ -350,7 +261,7 @@ export class DotLottieCommon { const audioAsset = animation.audioAssets.at(j); if (audioAsset) { - this._renameAudio(animation, `audio_${size}`, audioAsset.id); + await this._renameAudio(animation, `audio_${size}`, audioAsset.id); size -= 1; } } @@ -360,7 +271,7 @@ export class DotLottieCommon { protected _addLottieAnimation(animation: LottieAnimationCommon): DotLottieCommon { if (this._animationsMap.get(animation.id)) { - throw createError('Duplicate animation id detected, aborting.'); + throw new DotLottieError('Duplicate animation id detected, aborting.'); } this._animationsMap.set(animation.id, animation); @@ -409,14 +320,14 @@ export class DotLottieCommon { /** * Returns the desired animation * @param animationId - desired animation id - * @param inlineAssets - if true will inline the assets inside the data of the LottieAnimation + * @param inlineAssets - if true will inline the assets inside the data of the LottieAnimationV1 * @returns */ public async getAnimation( animationId: string, - options: GetAnimationOptions = {}, + options?: GetAnimationOptions, ): Promise { - if (!options.inlineAssets) return this._animationsMap.get(animationId); + if (!options?.inlineAssets) return this._animationsMap.get(animationId); let dataWithInlinedImages = this._animationsMap.get(animationId); @@ -438,7 +349,7 @@ export class DotLottieCommon { const assignedThemes = targetAnimation.themes; for (const assignedTheme of assignedThemes) { - this.unassignTheme({ + this.unscopeTheme({ animationId: targetAnimation.id, themeId: assignedTheme.id, }); @@ -477,40 +388,39 @@ export class DotLottieCommon { protected _buildManifest(): Manifest { const animationsList = Array.from(this._animationsMap.values()); const themesList = Array.from(this._themesMap.values()); - const stateMachinesList = Array.from(this._stateMachinesMap.keys()); + const stateMachinesList = Array.from(this._stateMachinesMap.values()); const activeAnimationId = animationsList.find((value) => value.defaultActiveAnimation)?.id ?? ''; const manifest: Manifest = { version: this.version, - revision: this.revision, - keywords: this.keywords, - author: this.author, generator: this.generator, animations: animationsList.map((animation) => ({ id: animation.id, - direction: animation.direction, - speed: animation.speed, - playMode: animation.playMode, - loop: animation.loop, - autoplay: animation.autoplay, - hover: animation.hover, - intermission: animation.intermission, - ...(animation.defaultTheme ? { defaultTheme: animation.defaultTheme } : {}), + ...(animation.name ? { name: animation.name } : {}), + ...(animation.initialTheme ? { initialTheme: animation.initialTheme } : {}), + ...(animation.background ? { background: animation.background } : {}), + ...(animation.themes.length > 0 ? { themes: animation.themes.map((theme) => theme.id) } : {}), })), - ...(this.description && this.description.trim() !== '' ? { description: this.description } : {}), - ...(activeAnimationId && activeAnimationId.trim() !== '' ? { activeAnimationId } : {}), - ...(this._customData && Object.keys(this._customData).length !== 0 ? { custom: this._customData } : {}), }; if (themesList.length > 0) { manifest.themes = themesList.map((theme) => ({ id: theme.id, - animations: theme.animations.map((animation) => animation.id), + ...(theme.name ? { name: theme.name } : {}), })); } if (stateMachinesList.length > 0) { - manifest.states = stateMachinesList; + manifest.stateMachines = stateMachinesList.map((stateMachine) => ({ + id: stateMachine.id, + ...(stateMachine.name ? { name: stateMachine.name } : {}), + })); + } + + if (activeAnimationId) { + manifest.initial = { + animation: activeAnimationId, + }; } return manifest; @@ -529,14 +439,10 @@ export class DotLottieCommon { await animation.toJSON(); } - for (const theme of this.themes) { - await theme.toString(); - } - if (this.animations.length > 1) { // Rename assets incrementally if there are multiple animations await this._renameImageAssets(); - this._renameAudioAssets(); + await this._renameAudioAssets(); } const parallelPlugins = []; @@ -573,13 +479,13 @@ export class DotLottieCommon { * @returns DotLottie instance */ public async fromURL(url: string): Promise { - if (!isValidURL(url)) throw createError('Invalid URL'); + if (!isValidURL(url)) throw new DotLottieError('Invalid URL'); try { const response = await fetch(url); if (!response.ok) { - throw createError(response.statusText); + throw new DotLottieError(response.statusText); } const arrayBuffer = await response.arrayBuffer(); @@ -587,46 +493,42 @@ export class DotLottieCommon { return this.fromArrayBuffer(arrayBuffer); } catch (err) { if (err instanceof Error) { - throw createError(err.message); + throw new DotLottieError(err.message); } } - throw createError('Unknown error'); + throw new DotLottieError('Unknown error'); } public merge(...dotlotties: DotLottieCommon[]): DotLottieCommon { const mergedDotlottie = this.create(); for (const dotlottie of dotlotties) { + dotlottie.themes.forEach((theme) => { + mergedDotlottie.addTheme({ + id: theme.id, + name: theme.name, + data: theme.data, + }); + }); + dotlottie.animations.forEach((animation) => { if (animation.data) { mergedDotlottie.addAnimation({ id: animation.id, + name: animation.name, data: animation.data, }); } else if (animation.url) { mergedDotlottie.addAnimation({ id: animation.id, + name: animation.name, url: animation.url, }); } - }); - - dotlottie.themes.forEach((theme) => { - if (theme.data) { - mergedDotlottie.addTheme({ - id: theme.id, - data: theme.data, - }); - } else if (theme.url) { - mergedDotlottie.addTheme({ - id: theme.id, - url: theme.url, - }); - } - theme.animations.forEach((animation) => { - mergedDotlottie.assignTheme({ + animation.themes.forEach((theme) => { + mergedDotlottie.scopeTheme({ animationId: animation.id, themeId: theme.id, }); @@ -635,10 +537,14 @@ export class DotLottieCommon { dotlottie.stateMachines.forEach((stateMachine) => { const stateOption = { - states: stateMachine.states, - descriptor: { id: stateMachine.id, initial: stateMachine.initial }, - listeners: stateMachine.listeners, - triggers: stateMachine.triggers, + id: stateMachine.id, + name: stateMachine.name, + data: { + states: stateMachine.states, + initial: stateMachine.initial, + listeners: stateMachine.listeners, + triggers: stateMachine.triggers, + }, zipOptions: stateMachine.zipOptions, }; @@ -661,13 +567,10 @@ export class DotLottieCommon { const targetTheme = this._themesMap.get(id); if (targetTheme) { - const scopedAnimations = targetTheme.animations; - - for (const scopedAnimation of scopedAnimations) { - this.unassignTheme({ - animationId: scopedAnimation.id, - themeId: id, - }); + for (const animation of this.animations) { + if (animation.themes.includes(targetTheme)) { + animation.unscopeTheme(targetTheme.id); + } } this._themesMap.delete(targetTheme.id); @@ -676,34 +579,30 @@ export class DotLottieCommon { return this; } - public assignTheme({ animationId, themeId }: { animationId: string; themeId: string }): DotLottieCommon { + public scopeTheme({ animationId, themeId }: { animationId: string; themeId: string }): DotLottieCommon { const theme = this._themesMap.get(themeId); - if (!theme) throw createError(`Failed to find theme with id ${themeId}`); + if (!theme) throw new DotLottieError(`Failed to find theme with id ${themeId}`); const animation = this._animationsMap.get(animationId); - if (!animation) throw createError(`Failed to find animation with id ${animationId}`); - - theme.addAnimation(animation); + if (!animation) throw new DotLottieError(`Failed to find animation with id ${animationId}`); - animation.addTheme(theme); + animation.scopeTheme(theme); return this; } - public unassignTheme({ animationId, themeId }: { animationId: string; themeId: string }): DotLottieCommon { + public unscopeTheme({ animationId, themeId }: { animationId: string; themeId: string }): DotLottieCommon { const theme = this._themesMap.get(themeId); - if (!theme) throw createError(`Failed to find theme with id ${themeId}`); + if (!theme) throw new DotLottieError(`Failed to find theme with id ${themeId}`); const animation = this._animationsMap.get(animationId); - if (!animation) throw createError(`Failed to find animation with id ${animationId}`); - - theme.removeAnimation(animation.id); + if (!animation) throw new DotLottieError(`Failed to find animation with id ${animationId}`); - animation.removeTheme(theme.id); + animation.unscopeTheme(theme.id); return this; } @@ -711,7 +610,7 @@ export class DotLottieCommon { public addStateMachine(stateMachineOptions: DotLottieStateMachineCommonOptions): DotLottieCommon { const newState = new DotLottieStateMachineCommon(stateMachineOptions); - this._stateMachinesMap.set(stateMachineOptions.descriptor.id, newState); + this._stateMachinesMap.set(stateMachineOptions.id, newState); return this; } @@ -726,29 +625,25 @@ export class DotLottieCommon { return this; } - protected _requireValidAuthor(author: string | undefined): asserts author is string { - if (typeof author !== 'string') throw createError('Invalid author'); - } - protected _requireValidDescription(description: string | undefined): asserts description is string { - if (typeof description !== 'string') throw createError('Invalid description'); + if (typeof description !== 'string') throw new DotLottieError('Invalid description'); } protected _requireValidGenerator(generator: string | undefined): asserts generator is string { - if (typeof generator !== 'string') throw createError('Invalid generator'); + if (typeof generator !== 'string') throw new DotLottieError('Invalid generator'); } protected _requireValidKeywords(keywords: string | undefined): asserts keywords is string { - if (typeof keywords !== 'string') throw createError('Invalid keywords'); + if (typeof keywords !== 'string') throw new DotLottieError('Invalid keywords'); } protected _requireValidVersion(version: string | undefined): asserts version is string { - if (typeof version !== 'string') throw createError('Invalid version'); + if (typeof version !== 'string') throw new DotLottieError('Invalid version'); } protected _requireValidCustomData( customData: Record | undefined, ): asserts customData is Record { - if (!customData) throw createError('Invalid customData'); + if (!customData) throw new DotLottieError('Invalid customData'); } } diff --git a/packages/dotlottie-js/src/common/lottie-image-common.ts b/packages/dotlottie-js/src/v2/common/image.ts similarity index 74% rename from packages/dotlottie-js/src/common/lottie-image-common.ts rename to packages/dotlottie-js/src/v2/common/image.ts index 31f40040..d1d29f33 100644 --- a/packages/dotlottie-js/src/common/lottie-image-common.ts +++ b/packages/dotlottie-js/src/v2/common/image.ts @@ -4,15 +4,16 @@ import type { ZipOptions } from 'fflate'; -import type { LottieAnimationCommon } from './lottie-animation-common'; -import { dataUrlFromU8, DotLottieError, getMimeTypeFromBase64 } from './utils'; +import type { ImageData } from '../../types'; +import { dataUrlFromU8, DotLottieError, getExtensionTypeFromBase64 } from '../../utils'; -export type ImageData = string | ArrayBuffer | Blob; +import type { LottieAnimationCommon } from './animation'; export interface ImageOptions { data?: ImageData; fileName: string; id: string; + lottieAssetId: string; parentAnimations?: LottieAnimationCommon[]; zipOptions?: ZipOptions; } @@ -20,8 +21,16 @@ export interface ImageOptions { export class LottieImageCommon { protected _data?: ImageData; + /** + * Unique id for the LottieImageCommon object. This is never modified. + */ protected _id: string = ''; + /** + * Asset id representing the image asset inside the Lottie animation. This can be modified. + */ + protected _lottieAssetId: string = ''; + protected _fileName: string = ''; protected _parentAnimations: LottieAnimationCommon[]; @@ -30,6 +39,7 @@ export class LottieImageCommon { public constructor(options: ImageOptions) { this._requireValidId(options.id); + this._requireValidLottieAssetId(options.lottieAssetId); this._requireValidFileName(options.fileName); this._zipOptions = options.zipOptions ?? {}; @@ -42,6 +52,10 @@ export class LottieImageCommon { this._id = options.id; } + if (options.lottieAssetId) { + this._lottieAssetId = options.lottieAssetId; + } + if (options.fileName) { this._fileName = options.fileName; } @@ -67,6 +81,16 @@ export class LottieImageCommon { if (!id) throw new DotLottieError('Invalid image id'); } + /** + * Ensure that the provided id is a valid string. + * The id must be a non-empty string, otherwise an error will be thrown. + * @param id - The id to validate. + * @throws Error - if the id is not a valid string. + */ + private _requireValidLottieAssetId(id: string | undefined): asserts id is string { + if (!id) throw new DotLottieError('Invalid Lottie Image Asset Id'); + } + /** * Ensure that the provided fileName is a valid string. * The fileName must be a non-empty string, otherwise an error will be thrown. @@ -97,6 +121,15 @@ export class LottieImageCommon { this._id = id; } + public get lottieAssetId(): string { + return this._lottieAssetId; + } + + public set lottieAssetId(id: string) { + this._requireValidLottieAssetId(id); + this._lottieAssetId = id; + } + public get data(): ImageData | undefined { return this._data; } @@ -129,14 +162,18 @@ export class LottieImageCommon { * Renames the id and fileName to newName. * @param newName - A new id and filename for the image. */ - public async renameImage(newName: string): Promise { - this.id = newName; + public async renameImage(newLottieAssetId: string): Promise { + this._lottieAssetId = newLottieAssetId; const data = await this.toDataURL(); - const mimeType = await getMimeTypeFromBase64(data); + const ext = await getExtensionTypeFromBase64(data); + + if (!ext) { + throw new DotLottieError('File extension type could not be detected from asset file.'); + } - this.fileName = `${newName}.${mimeType ? mimeType.split('/')[1] : 'png'}`; + this.fileName = `${newLottieAssetId}.${ext}`; } public async toArrayBuffer(): Promise { diff --git a/packages/dotlottie-js/src/v2/common/index.ts b/packages/dotlottie-js/src/v2/common/index.ts new file mode 100644 index 00000000..524905b5 --- /dev/null +++ b/packages/dotlottie-js/src/v2/common/index.ts @@ -0,0 +1,11 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './audio'; +export * from './theme'; +export * from './state-machine'; +export * from './schemas'; diff --git a/packages/dotlottie-js/src/common/dotlottie-plugin.ts b/packages/dotlottie-js/src/v2/common/plugin.ts similarity index 74% rename from packages/dotlottie-js/src/common/dotlottie-plugin.ts rename to packages/dotlottie-js/src/v2/common/plugin.ts index 7fa867ef..9c73f8ea 100644 --- a/packages/dotlottie-js/src/common/dotlottie-plugin.ts +++ b/packages/dotlottie-js/src/v2/common/plugin.ts @@ -2,8 +2,9 @@ * Copyright 2023 Design Barn Inc. */ -import type { DotLottieCommon } from './dotlottie-common'; -import { createError } from './utils'; +import { DotLottieError } from '../../utils'; + +import type { DotLottieCommon } from './dotlottie'; interface DotLottiePluginOptions { parallel?: boolean; @@ -39,10 +40,10 @@ export class DotLottiePlugin { } public async onBuild(): Promise { - throw createError('dotlottie-plugin build Not implemented!'); + throw new DotLottieError('dotlottie-plugin build Not implemented!'); } protected _requireDotLottie(dotLottie: DotLottieCommon | undefined): asserts dotLottie { - if (!dotLottie) throw createError('dotLottie context is null inside of duplicate image detector plugin.'); + if (!dotLottie) throw new DotLottieError('dotLottie context is null inside of duplicate image detector plugin.'); } } diff --git a/packages/dotlottie-js/src/common/duplicate-image-detector-common.ts b/packages/dotlottie-js/src/v2/common/plugins/duplicate-image-detector.ts similarity index 87% rename from packages/dotlottie-js/src/common/duplicate-image-detector-common.ts rename to packages/dotlottie-js/src/v2/common/plugins/duplicate-image-detector.ts index 0a0b14d5..e2f2395e 100644 --- a/packages/dotlottie-js/src/common/duplicate-image-detector-common.ts +++ b/packages/dotlottie-js/src/v2/common/plugins/duplicate-image-detector.ts @@ -4,12 +4,11 @@ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; -import { LottieImage } from '../lottie-image'; - -import { DotLottiePlugin } from './dotlottie-plugin'; -import type { LottieAnimationCommon } from './lottie-animation-common'; -import type { LottieImageCommon } from './lottie-image-common'; -import { createError } from './utils'; +import { DotLottieError } from '../../../utils'; +import { LottieImage } from '../../browser/image'; +import type { LottieAnimationCommon } from '../animation'; +import type { LottieImageCommon } from '../image'; +import { DotLottiePlugin } from '../plugin'; interface LottieImageCompare { excludeFromExport: boolean; @@ -19,17 +18,15 @@ interface LottieImageCompare { export class DuplicateImageDetectorCommon extends DotLottiePlugin { public async generatePhash(_image: LottieImageCommon): Promise { - createError('generatePhash(image: LottieImageCommon): Promise is not implemented in concrete class.'); - - return ''; + throw new DotLottieError( + 'generatePhash(image: LottieImageCommon): Promise is not implemented in concrete class.', + ); } public distanceTo(_imageHash: string, _targetImageHash: string): number { - createError( + throw new DotLottieError( 'distanceTo(_imageHash: string, _targetImageHash: string): Promise is not implemented in concrete class.', ); - - return 0; } private async _createRecordOfDuplicates(): Promise> { @@ -55,7 +52,7 @@ export class DuplicateImageDetectorCommon extends DotLottiePlugin { // Now that we have a single image of the image array, compare it to every other image in the arry for (const compareImage of images) { if ( - image.image.id !== compareImage.image.id && + image.image.lottieAssetId !== compareImage.image.lottieAssetId && !image.excludeFromExport && !compareImage.excludeFromExport && image.hash && @@ -70,7 +67,9 @@ export class DuplicateImageDetectorCommon extends DotLottiePlugin { recordOfDuplicates[image.image.fileName] = [compareImage.image]; } else if (recordOfDuplicates[compareImage.image.fileName]) { // Check for duplicates, otherwise push the duplicate image - if (!recordOfDuplicates[compareImage.image.fileName]?.find((item) => item.id === image.image.id)) { + if ( + !recordOfDuplicates[compareImage.image.fileName]?.find((item) => item.id === image.image.lottieAssetId) + ) { image.excludeFromExport = true; recordOfDuplicates[compareImage.image.fileName]?.push(image.image); } @@ -142,6 +141,7 @@ export class DuplicateImageDetectorCommon extends DotLottiePlugin { clonedImages[key] = new LottieImage({ data: image.data, id: image.id, + lottieAssetId: image.lottieAssetId, fileName: image.fileName, }); } @@ -150,7 +150,7 @@ export class DuplicateImageDetectorCommon extends DotLottiePlugin { } if (Object.keys(clonedImages).length !== Object.keys(recordOfDuplicates).length) - createError('The number of cloned images does not match the number of duplicate keys.'); + throw new DotLottieError('The number of cloned images does not match the number of duplicate keys.'); // For each image of recordOfDuplicates, remove itself from all the parent animations and push the clone for (const key in recordOfDuplicates) { diff --git a/packages/dotlottie-js/src/v2/common/schemas/index.ts b/packages/dotlottie-js/src/v2/common/schemas/index.ts new file mode 100644 index 00000000..f3fae5bb --- /dev/null +++ b/packages/dotlottie-js/src/v2/common/schemas/index.ts @@ -0,0 +1,7 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './manifest'; +export * from './state-machine'; +export * from './theme'; diff --git a/packages/dotlottie-js/src/v2/common/schemas/manifest.ts b/packages/dotlottie-js/src/v2/common/schemas/manifest.ts new file mode 100644 index 00000000..ea2a31ba --- /dev/null +++ b/packages/dotlottie-js/src/v2/common/schemas/manifest.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import { type Output, string, array, optional, object } from 'valibot'; + +export const ManifestAnimationSchema = object({ + id: string(), + name: optional(string()), + initialTheme: optional(string()), + background: optional(string()), + themes: optional(array(string())), +}); +export type ManifestAnimation = Output; + +export const ManifestThemeSchema = object({ + id: string(), + name: optional(string()), +}); +export type ManifestTheme = Output; + +export const ManifestStateMachineSchema = object({ + id: string(), + name: optional(string()), +}); +export type ManifestStateMachine = Output; + +export const InitialObjectSchema = object({ + animation: optional(string()), + stateMachine: optional(string()), +}); + +export const ManifestSchema = object({ + version: string(), + generator: string(), + initial: optional(InitialObjectSchema), + animations: array(ManifestAnimationSchema), + themes: optional(array(ManifestThemeSchema)), + stateMachines: optional(array(ManifestStateMachineSchema)), +}); + +export type Manifest = Output; diff --git a/packages/dotlottie-js/src/common/dotlottie-state.ts b/packages/dotlottie-js/src/v2/common/schemas/state-machine.ts similarity index 95% rename from packages/dotlottie-js/src/common/dotlottie-state.ts rename to packages/dotlottie-js/src/v2/common/schemas/state-machine.ts index 85defe7f..dc25d66a 100644 --- a/packages/dotlottie-js/src/common/dotlottie-state.ts +++ b/packages/dotlottie-js/src/v2/common/schemas/state-machine.ts @@ -2,8 +2,7 @@ * Copyright 2023 Design Barn Inc. */ -import type { Output } from 'valibot'; -import { boolean, number, object, optional, string, array, union } from 'valibot'; +import { type Output, boolean, number, object, optional, string, array, union } from 'valibot'; export const NumericGuardSchema = object({ type: string('Numeric'), @@ -142,9 +141,10 @@ const StateType = union([string('PlaybackState'), string('GlobalState')]); export const PlaybackStateSchema = object({ name: string(), type: StateType, - animationId: string(), + animation: string(), loop: optional(boolean()), autoplay: optional(boolean()), + final: optional(boolean()), mode: optional(Modes), speed: optional(number()), segment: optional(string()), @@ -186,7 +186,6 @@ export const PointerEnterSchema = object({ export const PointerMoveSchema = object({ type: string(), - layerName: optional(string()), actions: array(ActionSchema), }); @@ -244,14 +243,7 @@ export const TriggerSchema = union([ export const TriggersSchema = array(TriggerSchema); -// Descriptor Schema -export const DescriptorSchema = object({ - id: string(), - initial: string(), -}); - export type DotLottieStates = Output; -export type DotLottieDescriptor = Output; export type DotLottieState = Output; export type DotLottieAction = Output; export type DotLottieNumericEvent = Output; @@ -268,7 +260,7 @@ export type DotLottieTransitions = Output; // DotLottieStateMachine Schema export const DotLottieStateMachineSchema = object({ - descriptor: DescriptorSchema, + initial: string(), states: StatesSchema, listeners: optional(ListenersSchema), triggers: optional(TriggersSchema), diff --git a/packages/dotlottie-js/src/v2/common/schemas/theme.ts b/packages/dotlottie-js/src/v2/common/schemas/theme.ts new file mode 100644 index 00000000..2925e180 --- /dev/null +++ b/packages/dotlottie-js/src/v2/common/schemas/theme.ts @@ -0,0 +1,110 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import type { Output } from 'valibot'; +import { object, union, number, array, optional, boolean, string, literal } from 'valibot'; + +const TangentSchema = object({ + x: union([number(), array(number())]), + y: union([number(), array(number())]), +}); + +const BaseKeyframeSchema = { + frame: number(), + inTangent: optional(TangentSchema), + outTangent: optional(TangentSchema), + hold: optional(boolean()), +}; + +const ScalarKeyframeSchema = object({ + ...BaseKeyframeSchema, + value: number(), +}); + +const ColorKeyframeSchema = object({ + ...BaseKeyframeSchema, + value: array(number()), +}); + +const BaseRuleSchema = { + animations: optional(array(string())), + id: string(), +}; + +const ScalarRuleSchema = object({ + ...BaseRuleSchema, + type: literal('Scalar'), + value: optional(number()), + keyframes: optional(array(ScalarKeyframeSchema)), + expression: optional(string()), +}); + +const PositionKeyframeSchema = object({ + ...BaseKeyframeSchema, + value: array(number()), + valueInTangent: optional(number()), + valueOutTangent: optional(number()), +}); + +const PositionRuleSchema = object({ + ...BaseRuleSchema, + type: literal('Position'), + split: optional(boolean()), + keyframes: optional(array(PositionKeyframeSchema)), + expression: optional(string()), +}); + +const ColorRuleSchema = object({ + ...BaseRuleSchema, + type: literal('Color'), + value: optional(array(number())), + keyframes: optional(array(ColorKeyframeSchema)), + expression: optional(string()), +}); + +const ImageRuleSchema = object({ + ...BaseRuleSchema, + type: literal('Image'), + value: object({ + id: optional(string()), + width: optional(number()), + height: optional(number()), + url: optional(string()), + }), +}); + +const GradientKeyframeSchema = object({ + ...BaseKeyframeSchema, + value: array( + object({ + color: array(number()), + offset: number(), + }), + ), +}); + +const GradientRuleSchema = object({ + ...BaseRuleSchema, + type: literal('Gradient'), + value: optional( + array( + object({ + color: array(number()), + offset: number(), + }), + ), + ), + keyframes: optional(array(GradientKeyframeSchema)), +}); + +const RuleSchema = union([ColorRuleSchema, ScalarRuleSchema, PositionRuleSchema, ImageRuleSchema, GradientRuleSchema]); + +export const RulesSchema = array(RuleSchema); + +export const ThemeDataSchema = object({ + rules: RulesSchema, +}); + +export type ThemeData = Output; +export type Rules = Output; diff --git a/packages/dotlottie-js/src/common/dotlottie-state-machine-common.ts b/packages/dotlottie-js/src/v2/common/state-machine.ts similarity index 68% rename from packages/dotlottie-js/src/common/dotlottie-state-machine-common.ts rename to packages/dotlottie-js/src/v2/common/state-machine.ts index 99fce8e5..0928a167 100644 --- a/packages/dotlottie-js/src/common/dotlottie-state-machine-common.ts +++ b/packages/dotlottie-js/src/v2/common/state-machine.ts @@ -5,26 +5,29 @@ import type { ZipOptions } from 'fflate'; import { safeParse, flatten } from 'valibot'; +import { DotLottieError, ErrorCodes } from '../../utils'; + import type { - DotLottieDescriptor, DotLottieListeners, + DotLottieStateMachine, DotLottieStates, DotLottieTransitions, DotLottieTriggers, -} from './dotlottie-state'; -import { DescriptorSchema, ListenersSchema, StatesSchema, TransitionsSchema, TriggersSchema } from './dotlottie-state'; -import { DotLottieError, ErrorCodes } from './utils'; - -export interface DotLottieStateMachineCommonOptions { - descriptor: DotLottieDescriptor; - listeners?: DotLottieListeners | undefined; - states: DotLottieStates; - triggers?: DotLottieTriggers | undefined; + ManifestStateMachine, +} from './schemas'; +import { ListenersSchema, StatesSchema, TransitionsSchema, TriggersSchema } from './schemas'; + +export interface DotLottieStateMachineCommonOptions extends ManifestStateMachine { + data: DotLottieStateMachine; zipOptions?: ZipOptions; } export class DotLottieStateMachineCommon { - protected _descriptor: DotLottieDescriptor; + protected _name: string | undefined; + + protected _id: string; + + protected _initial: string; protected _zipOptions: ZipOptions; @@ -35,23 +38,25 @@ export class DotLottieStateMachineCommon { protected _triggers: DotLottieTriggers; public constructor(options: DotLottieStateMachineCommonOptions) { - this._requireValidTriggers(options.triggers ?? []); - this._requireValidListeners(options.listeners ?? []); - this._requireValidId(options.descriptor.id); - this._requireValidStates(options.states); - this._requireValidDescriptor(options.descriptor); + this._requireValidId(options.id); + this._requireValidTriggers(options.data.triggers ?? []); + this._requireValidListeners(options.data.listeners ?? []); + this._requireValidStates(options.data.states); + this._requireValidInitial(options.data.initial, options.data.states); - this._descriptor = options.descriptor; + this._name = options.name; - this._zipOptions = options.zipOptions ?? {}; + this._id = options.id; + + this._initial = options.data.initial; - this._states = options.states; + this._zipOptions = options.zipOptions ?? {}; - this._descriptor = options.descriptor; + this._states = options.data.states; - this._listeners = options.listeners ?? []; + this._listeners = options.data.listeners ?? []; - this._triggers = options.triggers ?? []; + this._triggers = options.data.triggers ?? []; } public get zipOptions(): ZipOptions { @@ -63,13 +68,21 @@ export class DotLottieStateMachineCommon { } public get id(): string { - return this._descriptor.id; + return this._id; } public set id(id: string) { this._requireValidId(id); - this._descriptor.id = id; + this._id = id; + } + + public get name(): string | undefined { + return this._name; + } + + public set name(name: string | undefined) { + this._name = name; } public get states(): DotLottieStates { @@ -97,24 +110,16 @@ export class DotLottieStateMachineCommon { } public get initial(): string { - return this._descriptor.initial; + return this._initial; } public set initial(initial: string) { - this._descriptor.initial = initial; - } - - public get descriptor(): DotLottieDescriptor { - return this._descriptor; - } - - public set descriptor(descriptor: DotLottieDescriptor) { - this._descriptor = descriptor; + this.initial = initial; } public toString(): string { return JSON.stringify({ - descriptor: this._descriptor, + initial: this._initial, states: this._states, triggers: this._triggers, listeners: this._listeners, @@ -127,13 +132,25 @@ export class DotLottieStateMachineCommon { } } - protected _requireValidDescriptor(descriptor: DotLottieDescriptor): void { - const result = safeParse(DescriptorSchema, descriptor); + protected _requireValidInitial(initial: string, states: DotLottieStates): void { + const result = safeParse(StatesSchema, states); if (!result.success) { const error = `Invalid state machine declaration, ${JSON.stringify(flatten(result.issues).nested, null, 2)}`; - throw new DotLottieError(`Invalid descriptor: ${error}`, ErrorCodes.INVALID_STATEMACHINE); + throw new DotLottieError(`Invalid initial state: ${error}`, ErrorCodes.INVALID_STATEMACHINE); + } + + let found = false; + + for (const state of states) { + if (state.name === initial) { + found = true; + } + } + + if (!found) { + throw new DotLottieError(`Initial state not found.`, ErrorCodes.INVALID_STATEMACHINE); } } diff --git a/packages/dotlottie-js/src/v2/common/theme.ts b/packages/dotlottie-js/src/v2/common/theme.ts new file mode 100644 index 00000000..19524b30 --- /dev/null +++ b/packages/dotlottie-js/src/v2/common/theme.ts @@ -0,0 +1,92 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +import type { ZipOptions } from 'fflate'; +import { safeParse } from 'valibot'; + +import { DotLottieError } from '../../utils'; + +import type { ManifestTheme, ThemeData } from './schemas'; +import { ThemeDataSchema } from './schemas'; + +export interface ThemeOptions extends ManifestTheme { + data: ThemeData; + id: string; + zipOptions?: ZipOptions; +} + +export class LottieThemeCommon { + protected _data: ThemeData; + + protected _id: string; + + protected _name: string | undefined; + + protected _zipOptions: ZipOptions; + + public constructor(options: ThemeOptions) { + this._requireValidId(options.id); + this._requireValidData(options.data); + + this._name = options.name; + this._data = options.data; + this._id = options.id; + + this._zipOptions = options.zipOptions ?? {}; + } + + public get zipOptions(): ZipOptions { + return this._zipOptions; + } + + public set zipOptions(zipOptions: ZipOptions) { + this._zipOptions = zipOptions; + } + + public get id(): string { + return this._id; + } + + public set id(id: string) { + this._requireValidId(id); + + this._id = id; + } + + public get name(): string | undefined { + return this._name; + } + + public set name(name: string | undefined) { + this._name = name; + } + + public get data(): ThemeData { + return this._data; + } + + public set data(data: ThemeData) { + this._requireValidData(data); + + this._data = data; + } + + public async toString(): Promise { + return JSON.stringify(this._data); + } + + private _requireValidId(id: string | undefined): asserts id is string { + if (typeof id !== 'string' || !id) throw new DotLottieError('Invalid theme id'); + } + + private _requireValidData(data: ThemeData): asserts data is ThemeData { + const result = safeParse(ThemeDataSchema, data); + + if (!result.success) { + const issues = JSON.stringify(result.issues, null, 2); + + throw new DotLottieError(`Invalid theme data: ${issues}`); + } + } +} diff --git a/packages/dotlottie-js/src/v2/index.browser.ts b/packages/dotlottie-js/src/v2/index.browser.ts new file mode 100644 index 00000000..0e6df446 --- /dev/null +++ b/packages/dotlottie-js/src/v2/index.browser.ts @@ -0,0 +1,6 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './browser'; +export * from './common'; diff --git a/packages/dotlottie-js/src/v2/index.node.ts b/packages/dotlottie-js/src/v2/index.node.ts new file mode 100644 index 00000000..f4986472 --- /dev/null +++ b/packages/dotlottie-js/src/v2/index.node.ts @@ -0,0 +1,6 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +export * from './node'; +export * from './common'; diff --git a/packages/dotlottie-js/src/node/lottie-animation.ts b/packages/dotlottie-js/src/v2/node/animation.ts similarity index 91% rename from packages/dotlottie-js/src/node/lottie-animation.ts rename to packages/dotlottie-js/src/v2/node/animation.ts index 7dcd65cb..7a284931 100644 --- a/packages/dotlottie-js/src/node/lottie-animation.ts +++ b/packages/dotlottie-js/src/v2/node/animation.ts @@ -4,11 +4,12 @@ import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; +import { DotLottieError, getExtensionTypeFromBase64, isAudioAsset } from '../../utils'; import type { AnimationOptions } from '../common'; -import { DotLottieError, LottieAnimationCommon, getExtensionTypeFromBase64, isAudioAsset } from '../common'; +import { LottieAnimationCommon } from '../common'; -import { LottieAudio } from './lottie-audio'; -import { LottieImage } from './lottie-image'; +import { LottieAudio } from './audio'; +import { LottieImage } from './image'; export class LottieAnimation extends LottieAnimationCommon { public constructor(options: AnimationOptions) { @@ -64,13 +65,14 @@ export class LottieAnimation extends LottieAnimationCommon { new LottieImage({ data: asset.p, id: asset.id, + lottieAssetId: asset.id, fileName, parentAnimations: [this], }), ); asset.p = fileName; - asset.u = '/images/'; + asset.u = '/i/'; asset.e = 0; } } @@ -118,7 +120,7 @@ export class LottieAnimation extends LottieAnimationCommon { ); asset.p = fileName; - asset.u = '/audio/'; + asset.u = '/u/'; asset.e = 0; } } diff --git a/packages/dotlottie-js/src/lottie-audio.ts b/packages/dotlottie-js/src/v2/node/audio.ts similarity index 64% rename from packages/dotlottie-js/src/lottie-audio.ts rename to packages/dotlottie-js/src/v2/node/audio.ts index 3e2c744e..b480f681 100644 --- a/packages/dotlottie-js/src/lottie-audio.ts +++ b/packages/dotlottie-js/src/v2/node/audio.ts @@ -2,8 +2,8 @@ * Copyright 2023 Design Barn Inc. */ -import type { AudioOptions } from './common'; -import { LottieAudioCommon } from './common'; +import type { AudioOptions } from '../common'; +import { LottieAudioCommon } from '../common'; export class LottieAudio extends LottieAudioCommon { public constructor(options: AudioOptions) { diff --git a/packages/dotlottie-js/src/node/dotlottie.ts b/packages/dotlottie-js/src/v2/node/dotlottie.ts similarity index 59% rename from packages/dotlottie-js/src/node/dotlottie.ts rename to packages/dotlottie-js/src/v2/node/dotlottie.ts index 1454a4e1..c3821471 100644 --- a/packages/dotlottie-js/src/node/dotlottie.ts +++ b/packages/dotlottie-js/src/v2/node/dotlottie.ts @@ -2,77 +2,91 @@ * Copyright 2023 Design Barn Inc. */ +/* eslint-disable @typescript-eslint/no-use-before-define */ + import type { Animation as AnimationType } from '@lottie-animation-community/lottie-types'; import type { Zippable } from 'fflate'; import { strToU8, unzip, zip, strFromU8 } from 'fflate'; -import pkg from '../../package.json'; -import type { - DotLottieOptions, - DotLottiePlugin, - AnimationOptions, - ManifestAnimation, - Manifest, - ConversionOptions, -} from '../common'; +import type { ConversionOptions } from '../../types'; import { - createError, - DotLottieCommon, base64ToUint8Array, - getExtensionTypeFromBase64, DotLottieError, + getDotLottieVersion, + getExtensionTypeFromBase64, isAudioAsset, -} from '../common'; +} from '../../utils'; +import { DotLottieV1 } from '../../v1/node'; +import type { DotLottieOptions, AnimationOptions } from '../common'; +import { DotLottieCommon } from '../common'; +import type { Manifest } from '../common/schemas'; + +import { LottieAnimation } from './animation'; +import { LottieAudio } from './audio'; +import { LottieImage } from './image'; +import { DuplicateImageDetector } from './plugins/duplicate-image-detector'; + +export async function toDotLottieV2(arrayBuffer: ArrayBuffer): Promise { + const version = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + // Assume it's a v1 file if the version is not 2 + if (version !== '2') { + const dotLottieV2 = new DotLottie(); + const dotLottieV1 = await new DotLottieV1().fromArrayBuffer(arrayBuffer); + + const animationIds = dotLottieV1.animations.map((animation) => animation.id); + + for (const animationId of animationIds) { + const animation = await dotLottieV1.getAnimation(animationId, { inlineAssets: true }); + + if (animation && animation.data) { + dotLottieV2.addAnimation({ + data: animation.data, + id: animationId, + }); + } + } + + await dotLottieV2.build(); + + return dotLottieV2; + } -import { DuplicateImageDetector } from './duplicate-image-detector'; -import { LottieAnimation } from './lottie-animation'; -import { LottieAudio } from './lottie-audio'; -import { LottieImage } from './lottie-image'; + return new DotLottie().fromArrayBuffer(arrayBuffer); +} export class DotLottie extends DotLottieCommon { public constructor(options?: DotLottieOptions) { - const generator = options?.generator ?? `${pkg.name}/node@${pkg.version}`; - - super({ - ...options, - generator, - }); + super(options); - if (this.enableDuplicateImageOptimization) this.addPlugins(new DuplicateImageDetector()); - } + if (this.enableDuplicateImageOptimization) { + const plugin = new DuplicateImageDetector(); - public override addPlugins(...plugins: DotLottiePlugin[]): DotLottieCommon { - plugins.forEach((plugin) => { plugin.install(this); this._plugins.push(plugin); - }); - - return this; + } } public override create(): DotLottieCommon { return new DotLottie(); } - public override async toBase64(options: ConversionOptions | undefined): Promise { + public override async toBase64(options?: ConversionOptions): Promise { const data = await this.toArrayBuffer(options); return Buffer.from(data).toString('base64'); } - public override async download( - _fileName: string, - _options: ConversionOptions | undefined = undefined, - ): Promise { - throw createError('Cannot download dotlottie in a non-browser environment'); + public override async download(_fileName: string, _options?: ConversionOptions): Promise { + throw new DotLottieError('Cannot download dotlottie in a non-browser environment'); } public override addAnimation(animationOptions: AnimationOptions): DotLottieCommon { const animation = new LottieAnimation(animationOptions); if (this._animationsMap.get(animationOptions.id)) { - throw createError('Duplicate animation id detected, aborting.'); + throw new DotLottieError('Duplicate animation id detected, aborting.'); } this._animationsMap.set(animation.id, animation); @@ -80,23 +94,17 @@ export class DotLottie extends DotLottieCommon { return this; } - public override async toArrayBuffer(options: ConversionOptions | undefined): Promise { + public override async toArrayBuffer(options?: ConversionOptions): Promise { const manifest = this._buildManifest(); const dotlottie: Zippable = { - 'manifest.json': [ - strToU8(JSON.stringify(manifest)), - { - // no compression for manifest - level: 0, - }, - ], + 'manifest.json': [strToU8(JSON.stringify(manifest)), {}], }; for (const animation of this.animations) { const json = await animation.toJSON(); - dotlottie[`animations/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; + dotlottie[`a/${animation.id}.json`] = [strToU8(JSON.stringify(json)), animation.zipOptions]; const imageAssets = animation.imageAssets; const audioAssets = animation.audioAssets; @@ -105,26 +113,26 @@ export class DotLottie extends DotLottieCommon { // Assure we have a base64 encoded version of the image const dataAsString = await asset.toDataURL(); - dotlottie[`images/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + dotlottie[`i/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; } for (const asset of audioAssets) { // Assure we have a base64 encoded version of the audio const dataAsString = await asset.toDataURL(); - dotlottie[`audio/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; + dotlottie[`u/${asset.fileName}`] = [base64ToUint8Array(dataAsString), asset.zipOptions]; } } for (const theme of this.themes) { const themeData = await theme.toString(); - dotlottie[`themes/${theme.id}.json`] = [strToU8(themeData), theme.zipOptions]; + dotlottie[`t/${theme.id}.json`] = [strToU8(themeData), theme.zipOptions]; } for (const state of this.stateMachines) { const stateData = state.toString(); - dotlottie[`states/${state.id}.json`] = [strToU8(stateData), state.zipOptions]; + dotlottie[`s/${state.id}.json`] = [strToU8(stateData), state.zipOptions]; } const dotlottieArrayBuffer = await new Promise((resolve, reject) => { @@ -149,6 +157,12 @@ export class DotLottie extends DotLottieCommon { * @throws Error */ public override async fromArrayBuffer(arrayBuffer: ArrayBuffer): Promise { + const dotLottieVersion = await getDotLottieVersion(new Uint8Array(arrayBuffer)); + + if (dotLottieVersion !== '2') { + return toDotLottieV2(arrayBuffer); + } + const dotlottie = new DotLottie(); try { @@ -168,85 +182,61 @@ export class DotLottie extends DotLottieCommon { if (contentObj['manifest.json'] instanceof Uint8Array) { try { const manifest = JSON.parse(strFromU8(contentObj['manifest.json'], false)) as Manifest; - const { author, custom, description, generator, keywords, version } = manifest; - - if (author) { - this._requireValidAuthor(author); - dotlottie.setAuthor(author); - } - if (custom) { - this._requireValidCustomData(custom); - dotlottie.setCustomData(custom); - } - if (description) { - this._requireValidDescription(description); - dotlottie.setDescription(description); - } - if (generator) { - this._requireValidGenerator(generator); - dotlottie.setGenerator(generator); - } - if (keywords) { - this._requireValidKeywords(keywords); - dotlottie.setKeywords(keywords); - } - if (version) { - this._requireValidVersion(version); - dotlottie.setVersion(version); - } for (const key of Object.keys(contentObj)) { const decompressedFile = contentObj[key] as Uint8Array; const decodedStr = strFromU8(contentObj[key] as Uint8Array, false); - if (key.startsWith('animations/') && key.endsWith('.json')) { - // extract animationId from key as the key = `animations/${animationId}.json` - const animationId = /animations\/(.+)\.json/u.exec(key)?.[1]; + if (key.startsWith('a/') && key.endsWith('.json')) { + // extract animationId from key as the key = `a/${animationId}.json` + const animationId = /a\/(.+)\.json/u.exec(key)?.[1]; if (!animationId) { - throw createError('Invalid animation id'); + throw new DotLottieError('Invalid animation id'); } const animation = JSON.parse(decodedStr); - const animationSettings = manifest['animations'].find( - (anim: ManifestAnimation) => anim.id === animationId, - ); + const animationSettings = manifest.animations.find((anim) => anim.id === animationId); if (animationSettings === undefined) { - throw createError('Animation not found inside manifest'); + throw new DotLottieError('Animation not found inside manifest'); } dotlottie.addAnimation({ data: animation, ...animationSettings, }); - } else if (key.startsWith('images/')) { - // extract imageId from key as the key = `images/${imageId}.${ext}` - const imageId = /images\/(.+)\./u.exec(key)?.[1]; + } else if (key.startsWith('i/')) { + // extract imageId from key as the key = `i/${imageId}.${ext}` + const imageId = /i\/(.+)\./u.exec(key)?.[1]; if (!imageId) { - throw createError('Invalid image id'); + throw new DotLottieError('Invalid image id'); } const base64 = Buffer.from(decompressedFile).toString('base64'); const ext = await getExtensionTypeFromBase64(base64); + if (!ext) { + throw new DotLottieError('Unrecognized asset file format.'); + } // Push the images in to a temporary array const imgDataURL = `data:image/${ext};base64,${base64}`; tmpImages.push( new LottieImage({ id: imageId, + lottieAssetId: imageId, data: imgDataURL, fileName: key.split('/')[1] || '', }), ); - } else if (key.startsWith('audio/')) { + } else if (key.startsWith('u/')) { // Do audio extraction - // extract audioID from key as the key = `audio/${audioID}.${ext}` - const audioId = /audio\/(.+)\./u.exec(key)?.[1]; + // extract audioID from key as the key = `u/${audioID}.${ext}` + const audioId = /u\/(.+)\./u.exec(key)?.[1]; if (!audioId) { throw new DotLottieError('Invalid audio id'); @@ -266,42 +256,40 @@ export class DotLottie extends DotLottieCommon { fileName: key.split('/')[1] || '', }), ); - } else if (key.startsWith('themes/') && key.endsWith('.json')) { - // extract themeId from key as the key = `themes/${themeId}.json` - const themeId = /themes\/(.+)\.json/u.exec(key)?.[1]; + } else if (key.startsWith('t/') && key.endsWith('.json')) { + // extract themeId from key as the key = `t/${themeId}.json` + const themeId = /t\/(.+)\.json/u.exec(key)?.[1]; if (!themeId) { - throw createError('Invalid theme id'); + throw new DotLottieError('Invalid theme id'); } manifest.themes?.forEach((theme) => { if (theme.id === themeId) { dotlottie.addTheme({ id: theme.id, + name: theme.name, data: JSON.parse(decodedStr), }); - - theme.animations.forEach((animationId) => { - dotlottie.assignTheme({ - animationId, - themeId, - }); - }); } }); - } else if (key.startsWith('states/') && key.endsWith('.json')) { - // extract stateId from key as the key = `states/${stateId}.json` - const stateId = /states\/(.+)\.json/u.exec(key)?.[1]; + } else if (key.startsWith('s/') && key.endsWith('.json')) { + // extract stateId from key as the key = `s/${stateId}.json` + const stateId = /s\/(.+)\.json/u.exec(key)?.[1]; if (!stateId) { - throw createError('Invalid theme id'); + throw new DotLottieError('Invalid theme id'); } - manifest.states?.forEach((state) => { - if (state === stateId) { + manifest.stateMachines?.forEach((stateMachine) => { + if (stateMachine.id === stateId) { const decodedStateMachine = JSON.parse(decodedStr); - dotlottie.addStateMachine(decodedStateMachine); + dotlottie.addStateMachine({ + id: stateMachine.id, + name: stateMachine.name, + data: decodedStateMachine, + }); } }); } @@ -346,9 +334,11 @@ export class DotLottie extends DotLottieCommon { } } } - } catch (err: any) { + } catch (err: unknown) { // throw error as it's invalid json - throw new DotLottieError(`Invalid manifest inside buffer! ${err.message}`); + throw new DotLottieError( + `Invalid manifest inside buffer! ${err instanceof Error ? err.message : 'Unknown error'}`, + ); } } else { // throw error as it's invalid buffer diff --git a/packages/dotlottie-js/src/lottie-image.ts b/packages/dotlottie-js/src/v2/node/image.ts similarity index 64% rename from packages/dotlottie-js/src/lottie-image.ts rename to packages/dotlottie-js/src/v2/node/image.ts index df6bc2e3..92d87a68 100644 --- a/packages/dotlottie-js/src/lottie-image.ts +++ b/packages/dotlottie-js/src/v2/node/image.ts @@ -2,8 +2,8 @@ * Copyright 2023 Design Barn Inc. */ -import type { ImageOptions } from './common'; -import { LottieImageCommon } from './common'; +import type { ImageOptions } from '../common'; +import { LottieImageCommon } from '../common'; export class LottieImage extends LottieImageCommon { public constructor(options: ImageOptions) { diff --git a/packages/dotlottie-js/src/v2/node/index.ts b/packages/dotlottie-js/src/v2/node/index.ts new file mode 100644 index 00000000..8501bc95 --- /dev/null +++ b/packages/dotlottie-js/src/v2/node/index.ts @@ -0,0 +1,10 @@ +/** + * Copyright 2023 Design Barn Inc. + */ + +export * from './dotlottie'; +export * from './animation'; +export * from './image'; +export * from './theme'; +export * from './state-machine'; +export * from './audio'; diff --git a/packages/dotlottie-js/src/node/duplicate-image-detector.ts b/packages/dotlottie-js/src/v2/node/plugins/duplicate-image-detector.ts similarity index 74% rename from packages/dotlottie-js/src/node/duplicate-image-detector.ts rename to packages/dotlottie-js/src/v2/node/plugins/duplicate-image-detector.ts index e9acabf5..cb8494ec 100644 --- a/packages/dotlottie-js/src/node/duplicate-image-detector.ts +++ b/packages/dotlottie-js/src/v2/node/plugins/duplicate-image-detector.ts @@ -4,8 +4,9 @@ import phash from 'sharp-phash'; -import { createError, DuplicateImageDetectorCommon } from '../common'; -import type { LottieImageCommon } from '../common'; +import { DotLottieError } from '../../../utils'; +import type { LottieImageCommon } from '../../common'; +import { DuplicateImageDetectorCommon } from '../../common/plugins/duplicate-image-detector'; export class DuplicateImageDetector extends DuplicateImageDetectorCommon { public override distanceTo(imageHash: string, targetImageHash: string): number { @@ -22,7 +23,7 @@ export class DuplicateImageDetector extends DuplicateImageDetectorCommon { public override async generatePhash(image: LottieImageCommon): Promise { if (!image.data) { - createError("Can't generate phash value."); + throw new DotLottieError("Can't generate phash value."); } const nBuf = await image.toArrayBuffer(); diff --git a/packages/dotlottie-js/src/node/lottie-state-machine.ts b/packages/dotlottie-js/src/v2/node/state-machine.ts similarity index 100% rename from packages/dotlottie-js/src/node/lottie-state-machine.ts rename to packages/dotlottie-js/src/v2/node/state-machine.ts diff --git a/packages/dotlottie-js/src/lottie-theme.ts b/packages/dotlottie-js/src/v2/node/theme.ts similarity index 64% rename from packages/dotlottie-js/src/lottie-theme.ts rename to packages/dotlottie-js/src/v2/node/theme.ts index a72fee2b..a681fac2 100644 --- a/packages/dotlottie-js/src/lottie-theme.ts +++ b/packages/dotlottie-js/src/v2/node/theme.ts @@ -2,8 +2,8 @@ * Copyright 2023 Design Barn Inc. */ -import type { ThemeOptions } from './common'; -import { LottieThemeCommon } from './common'; +import type { ThemeOptions } from '../common'; +import { LottieThemeCommon } from '../common'; export class LottieTheme extends LottieThemeCommon { public constructor(options: ThemeOptions) { diff --git a/packages/dotlottie-js/tsconfig.json b/packages/dotlottie-js/tsconfig.json index b55fe392..7c426dde 100644 --- a/packages/dotlottie-js/tsconfig.json +++ b/packages/dotlottie-js/tsconfig.json @@ -1,8 +1,9 @@ { // Extend from the build config - "extends": "../../tsconfig.dev.json" + "extends": "../../tsconfig.dev.json", // Compiler options - // "compilerOptions": { - // } + "compilerOptions": { + "types": ["vite-plugin-arraybuffer/types"] + } } diff --git a/packages/dotlottie-js/tsup.config.cjs b/packages/dotlottie-js/tsup.config.cjs index aebad447..167435f1 100644 --- a/packages/dotlottie-js/tsup.config.cjs +++ b/packages/dotlottie-js/tsup.config.cjs @@ -20,17 +20,17 @@ const commonConfig = { export default defineConfig([ { ...commonConfig, - entry: ['./src/node/*.ts'], - outDir: './dist/node', + entry: ['./src/index.node.ts'], + outDir: './dist', platform: 'node', target: ['es2020', 'node18'], }, { ...commonConfig, - entry: ['./src/*.ts'], + entry: ['./src/index.browser.ts'], outDir: './dist', platform: 'browser', target: ['es2020'], - noExternal: ['browser-image-hash'], + noExternal: ['browser-image-hash', 'file-type'], }, ]); diff --git a/packages/dotlottie-js/types/index.d.ts b/packages/dotlottie-js/types/index.d.ts index 8f428d8b..6ed59875 100644 --- a/packages/dotlottie-js/types/index.d.ts +++ b/packages/dotlottie-js/types/index.d.ts @@ -6,12 +6,12 @@ declare module 'DifferenceHashBuilder'; declare module 'sharp-phash'; -declare module '*.lottie' { +declare module '*.lottie?uint8array' { const value: Uint8Array; export default value; } -declare module '*.txt' { +declare module '*.txt?raw' { const value: string; export default value; } diff --git a/packages/dotlottie-js/vitest.browser.config.js b/packages/dotlottie-js/vitest.browser.config.js new file mode 100644 index 00000000..120b76bd --- /dev/null +++ b/packages/dotlottie-js/vitest.browser.config.js @@ -0,0 +1,22 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import arraybuffer from 'vite-plugin-arraybuffer'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [arraybuffer()], + assetsInclude: '**/*.lottie', + test: { + include: ['./src/**/__tests__/browser/**/*.{test,spec}.ts', './src/**/__tests__/**/*.browser.{test,spec}.ts'], + name: 'browser', + browser: { + provider: 'playwright', + enabled: true, + name: 'chromium', + headless: true, + screenshotFailures: false, + }, + }, +}); diff --git a/packages/dotlottie-js/vitest.config.js b/packages/dotlottie-js/vitest.config.js new file mode 100644 index 00000000..b6577c6f --- /dev/null +++ b/packages/dotlottie-js/vitest.config.js @@ -0,0 +1,15 @@ +/** + * Copyright 2024 Design Barn Inc. + */ + +import arraybuffer from 'vite-plugin-arraybuffer'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [arraybuffer()], + assetsInclude: '**/*.lottie', + test: { + include: ['./src/**/__tests__/node/**/*.{test,spec}.ts', './src/**/__tests__/**/*.node.{test,spec}.ts'], + name: 'node', + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 098a6257..cb54686f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: devDependencies: '@changesets/cli': - specifier: 2.23.2 - version: 2.23.2 + specifier: 2.27.9 + version: 2.27.9 '@commitlint/cli': specifier: 17.0.3 version: 17.0.3 @@ -78,7 +78,7 @@ importers: apps/next: dependencies: '@dotlottie/dotlottie-js': - specifier: workspace:^ + specifier: workspace:* version: link:../../packages/dotlottie-js '@dotlottie/player-component': specifier: 1.3.2 @@ -108,22 +108,9 @@ importers: apps/node: dependencies: '@dotlottie/dotlottie-js': - specifier: workspace:^ + specifier: workspace:* version: link:../../packages/dotlottie-js - apps/nuxt: - dependencies: - '@dotlottie/dotlottie-js': - specifier: workspace:^ - version: link:../../packages/dotlottie-js - devDependencies: - '@types/node': - specifier: ^18 - version: 18.0.6 - nuxt: - specifier: ^3.4.2 - version: 3.4.2(@types/node@18.0.6)(encoding@0.1.13)(eslint@8.39.0)(optionator@0.9.1)(rollup@3.21.0)(terser@5.17.1)(typescript@5.6.3)(vue-tsc@2.1.10(typescript@5.6.3)) - apps/react: dependencies: '@dotlottie/player-component': @@ -143,7 +130,7 @@ importers: version: 18.2.0(react@18.2.0) devDependencies: '@dotlottie/dotlottie-js': - specifier: workspace:^ + specifier: workspace:* version: link:../../packages/dotlottie-js '@types/react': specifier: ^18.0.28 @@ -159,7 +146,7 @@ importers: version: 5.59.1(eslint@8.39.0)(typescript@5.0.4) '@vitejs/plugin-react': specifier: ^4.0.0-beta.0 - version: 4.0.0(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1)) + version: 4.0.0(vite@4.3.2(@types/node@22.8.7)(terser@5.17.1)) eslint: specifier: ^8.38.0 version: 8.39.0 @@ -174,7 +161,7 @@ importers: version: 5.0.4 vite: specifier: ^4.3.0 - version: 4.3.2(@types/node@18.0.6)(terser@5.17.1) + version: 4.3.2(@types/node@22.8.7)(terser@5.17.1) apps/vue: dependencies: @@ -183,20 +170,20 @@ importers: version: 1.1.0 vue: specifier: ^3.2.47 - version: 3.2.47 + version: 3.5.12(typescript@5.6.3) devDependencies: '@dotlottie/dotlottie-js': - specifier: workspace:^ + specifier: workspace:* version: link:../../packages/dotlottie-js '@vitejs/plugin-vue': specifier: ^4.1.0 - version: 4.2.0(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue@3.2.47) + version: 4.2.0(vite@4.3.2(@types/node@22.8.7)(terser@5.17.1))(vue@3.5.12(typescript@5.6.3)) typescript: specifier: ^5.6.3 version: 5.6.3 vite: specifier: ^4.3.0 - version: 4.3.2(@types/node@18.0.6)(terser@5.17.1) + version: 4.3.2(@types/node@22.8.7)(terser@5.17.1) vue-tsc: specifier: ^2.1.10 version: 2.1.10(typescript@5.6.3) @@ -229,38 +216,41 @@ importers: specifier: 4.3.5 version: 4.3.5 '@types/node': - specifier: 18.0.6 - version: 18.0.6 + specifier: 22.8.7 + version: 22.8.7 '@types/sharp': specifier: 0.31.1 version: 0.31.1 + '@vitest/browser': + specifier: 2.1.3 + version: 2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3) + '@vitest/coverage-v8': + specifier: 2.1.3 + version: 2.1.3(@vitest/browser@2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3))(vitest@2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1)) cross-env: specifier: 7.0.3 version: 7.0.3 - esbuild: - specifier: 0.14.49 - version: 0.14.49 - jasmine: - specifier: 5.1.0 - version: 5.1.0 - jasmine-browser-runner: - specifier: 2.2.0 - version: 2.2.0(jasmine-core@5.1.1) - jasmine-core: - specifier: 5.1.1 - version: 5.1.1 js-base64: specifier: 3.7.5 version: 3.7.5 nodemon: specifier: 2.0.20 version: 2.0.20 + playwright: + specifier: ^1.48.2 + version: 1.48.2 tsup: - specifier: 6.1.3 - version: 6.1.3(postcss@8.4.23)(ts-node@10.9.1(@types/node@18.0.6)(typescript@4.7.4))(typescript@4.7.4) + specifier: 8.3.0 + version: 8.3.0(postcss@8.4.47)(typescript@4.7.4)(yaml@2.6.0) typescript: specifier: 4.7.4 version: 4.7.4 + vite-plugin-arraybuffer: + specifier: ^0.0.8 + version: 0.0.8 + vitest: + specifier: ^2.1.3 + version: 2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1) packages: @@ -273,6 +263,10 @@ packages: resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==} engines: {node: '>=6.0.0'} + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + '@ardatan/sync-fetch@0.0.1': resolution: {integrity: sha512-xhlTqH0m31mnsG0tIP4ETgfSB6gXDaYYsUWTrlUV93fFQPI9dd8hE0Ot6MHLCtqgB32hwJAC3YZMWlXZw7AleA==} engines: {node: '>=14'} @@ -303,22 +297,12 @@ packages: resolution: {integrity: sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.18.6': - resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==} - engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.21.4': resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-class-features-plugin@7.21.4': - resolution: {integrity: sha512-46QrX2CQlaFRF4TkwfTt6nJD7IHq8539cCL7SDpqWSDeJKY1xylKKY5F/33mJhLZ3mFvKv2gGrVS6NkyF6qs+Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-environment-visitor@7.18.9': resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==} engines: {node: '>=6.9.0'} @@ -331,10 +315,6 @@ packages: resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==} engines: {node: '>=6.9.0'} - '@babel/helper-member-expression-to-functions@7.21.0': - resolution: {integrity: sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.21.4': resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==} engines: {node: '>=6.9.0'} @@ -343,26 +323,14 @@ packages: resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==} engines: {node: '>=6.9.0'} - '@babel/helper-optimise-call-expression@7.18.6': - resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.20.2': resolution: {integrity: sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.20.7': - resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==} - engines: {node: '>=6.9.0'} - '@babel/helper-simple-access@7.20.2': resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==} engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.20.0': - resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==} - engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.18.6': resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==} engines: {node: '>=6.9.0'} @@ -400,23 +368,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} + '@babel/parser@7.25.9': + resolution: {integrity: sha512-aI3jjAAO1fh7vY/pBGsn1i9LDbRP43+asrRlkPuTXW5yHXtd1NgTEMudbBoDDxrf1daEEfPJqR+JBMakzrR4Dg==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-jsx@7.21.4': - resolution: {integrity: sha512-5hewiLct5OKyh6PLKEYaFclcqtIgCb6bmELouxjF6up5q3Sov7rOayW4RwhbaBL0dit8rA80GNfY+UuDp2mBbQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.21.4': - resolution: {integrity: sha512-xz0D39NvhQn4t4RNsHmDnnsaQizIlUkdtYvLs8La1BlfjQ6JEwxkJGeqJMW2tAXx+q6H+WFuUTXNdYVpEya0YA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-self@7.21.0': resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==} engines: {node: '>=6.9.0'} @@ -429,28 +385,14 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.21.3': - resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/runtime-corejs3@7.21.0': resolution: {integrity: sha512-TDD4UJzos3JJtM+tHX+w2Uc+KWj7GV+VKKFdMVd2Rx8sdA19hcc3P3AHFYd5LVOw+pYuSd5lICC3gm52B6Rwxw==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.20.13': - resolution: {integrity: sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA==} - engines: {node: '>=6.9.0'} - '@babel/runtime@7.21.0': resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} engines: {node: '>=6.9.0'} - '@babel/standalone@7.21.4': - resolution: {integrity: sha512-Rw4nGqH/iyVeYxARKcz7iGP+njkPsVqJ45TmXMONoGoxooWjXCAs+CUcLeAZdBGCLqgaPvHKCYvIaDT2Iq+KfA==} - engines: {node: '>=6.9.0'} - '@babel/template@7.20.7': resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==} engines: {node: '>=6.9.0'} @@ -463,68 +405,80 @@ packages: resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==} engines: {node: '>=6.9.0'} - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} + '@babel/types@7.25.9': + resolution: {integrity: sha512-OwS2CM5KocvQ/k7dFJa8i5bNGJP0hXWfVCfDkqRFP1IreH1JDC7wG6eCYCi0+McbfT8OR/kNqsI0UU0xP9H6PQ==} engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + '@braintree/sanitize-url@3.1.0': resolution: {integrity: sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==} deprecated: Potential XSS vulnerability patched in v6.0.0. - '@changesets/apply-release-plan@6.1.3': - resolution: {integrity: sha512-ECDNeoc3nfeAe1jqJb5aFQX7CqzQhD2klXRez2JDb/aVpGUbX673HgKrnrgJRuQR/9f2TtLoYIzrGB9qwD77mg==} + '@bundled-es-modules/cookie@2.0.0': + resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + + '@changesets/apply-release-plan@7.0.5': + resolution: {integrity: sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==} - '@changesets/assemble-release-plan@5.2.3': - resolution: {integrity: sha512-g7EVZCmnWz3zMBAdrcKhid4hkHT+Ft1n0mLussFMcB1dE2zCuwcvGoy9ec3yOgPGF4hoMtgHaMIk3T3TBdvU9g==} + '@changesets/assemble-release-plan@6.0.4': + resolution: {integrity: sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==} - '@changesets/changelog-git@0.1.14': - resolution: {integrity: sha512-+vRfnKtXVWsDDxGctOfzJsPhaCdXRYoe+KyWYoq5X/GqoISREiat0l3L8B0a453B2B4dfHGcZaGyowHbp9BSaA==} + '@changesets/changelog-git@0.2.0': + resolution: {integrity: sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==} - '@changesets/cli@2.23.2': - resolution: {integrity: sha512-o7CWC+mcwOmA3yK5axqHOSYPYEjX/x+nq/s9aX78AyzH1SQZa6L5HX4P9uUXibyjcKynklkmusxv8vN8+hJggA==} + '@changesets/cli@2.27.9': + resolution: {integrity: sha512-q42a/ZbDnxPpCb5Wkm6tMVIxgeI9C/bexntzTeCFBrQEdpisQqk8kCHllYZMDjYtEc1ZzumbMJAG8H0Z4rdvjg==} hasBin: true - '@changesets/config@2.3.0': - resolution: {integrity: sha512-EgP/px6mhCx8QeaMAvWtRrgyxW08k/Bx2tpGT+M84jEdX37v3VKfh4Cz1BkwrYKuMV2HZKeHOh8sHvja/HcXfQ==} + '@changesets/config@3.0.3': + resolution: {integrity: sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==} - '@changesets/errors@0.1.4': - resolution: {integrity: sha512-HAcqPF7snsUJ/QzkWoKfRfXushHTu+K5KZLJWPb34s4eCZShIf8BFO3fwq6KU8+G7L5KdtN2BzQAXOSXEyiY9Q==} + '@changesets/errors@0.2.0': + resolution: {integrity: sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==} - '@changesets/get-dependents-graph@1.3.5': - resolution: {integrity: sha512-w1eEvnWlbVDIY8mWXqWuYE9oKhvIaBhzqzo4ITSJY9hgoqQ3RoBqwlcAzg11qHxv/b8ReDWnMrpjpKrW6m1ZTA==} + '@changesets/get-dependents-graph@2.1.2': + resolution: {integrity: sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==} - '@changesets/get-release-plan@3.0.16': - resolution: {integrity: sha512-OpP9QILpBp1bY2YNIKFzwigKh7Qe9KizRsZomzLe6pK8IUo8onkAAVUD8+JRKSr8R7d4+JRuQrfSSNlEwKyPYg==} + '@changesets/get-release-plan@4.0.4': + resolution: {integrity: sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==} - '@changesets/get-version-range-type@0.3.2': - resolution: {integrity: sha512-SVqwYs5pULYjYT4op21F2pVbcrca4qA/bAA3FmFXKMN7Y+HcO8sbZUTx3TAy2VXulP2FACd1aC7f2nTuqSPbqg==} + '@changesets/get-version-range-type@0.4.0': + resolution: {integrity: sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==} - '@changesets/git@1.5.0': - resolution: {integrity: sha512-Xo8AT2G7rQJSwV87c8PwMm6BAc98BnufRMsML7m7Iw8Or18WFvFmxqG5aOL5PBvhgq9KrKvaeIBNIymracSuHg==} + '@changesets/git@3.0.1': + resolution: {integrity: sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==} - '@changesets/git@2.0.0': - resolution: {integrity: sha512-enUVEWbiqUTxqSnmesyJGWfzd51PY4H7mH9yUw0hPVpZBJ6tQZFMU3F3mT/t9OJ/GjyiM4770i+sehAn6ymx6A==} + '@changesets/logger@0.1.1': + resolution: {integrity: sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==} - '@changesets/logger@0.0.5': - resolution: {integrity: sha512-gJyZHomu8nASHpaANzc6bkQMO9gU/ib20lqew1rVx753FOxffnCrJlGIeQVxNWCqM+o6OOleCo/ivL8UAO5iFw==} + '@changesets/parse@0.4.0': + resolution: {integrity: sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==} - '@changesets/parse@0.3.16': - resolution: {integrity: sha512-127JKNd167ayAuBjUggZBkmDS5fIKsthnr9jr6bdnuUljroiERW7FBTDNnNVyJ4l69PzR57pk6mXQdtJyBCJKg==} + '@changesets/pre@2.0.1': + resolution: {integrity: sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==} - '@changesets/pre@1.0.14': - resolution: {integrity: sha512-dTsHmxQWEQekHYHbg+M1mDVYFvegDh9j/kySNuDKdylwfMEevTeDouR7IfHNyVodxZXu17sXoJuf2D0vi55FHQ==} + '@changesets/read@0.6.1': + resolution: {integrity: sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==} - '@changesets/read@0.5.9': - resolution: {integrity: sha512-T8BJ6JS6j1gfO1HFq50kU3qawYxa4NTbI/ASNVVCBTsKquy2HYwM9r7ZnzkiMe8IEObAJtUVGSrePCOxAK2haQ==} + '@changesets/should-skip-package@0.1.1': + resolution: {integrity: sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==} '@changesets/types@4.1.0': resolution: {integrity: sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==} - '@changesets/types@5.2.1': - resolution: {integrity: sha512-myLfHbVOqaq9UtUKqR/nZA/OY7xFjQMdfgfqeZIBK4d0hA6pgxArvdv8M+6NUzzBsjWLOtvApv8YHr4qM+Kpfg==} + '@changesets/types@6.0.0': + resolution: {integrity: sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==} - '@changesets/write@0.1.9': - resolution: {integrity: sha512-E90ZrsrfJVOOQaP3Mm5Xd7uDwBAqq3z5paVEavTHKA8wxi7NAL8CmjgbGxSFuiP7ubnJA2BuHlrdE4z86voGOg==} + '@changesets/write@0.3.2': + resolution: {integrity: sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==} '@clinic/bubbleprof@8.0.1': resolution: {integrity: sha512-pbV0x/64jcXeou+6RWrZPyvZpnNQ+PBwgBrGKWQNmBig6qdwrQMosXl1GIx16GaGYcetOBIxJ7HHujtywgNwdQ==} @@ -553,9 +507,6 @@ packages: '@clinic/trace-events-parser@2.0.0': resolution: {integrity: sha512-hXpT4xJED7kW0+BNCSNSFNlYZO0xMYIBbx/lM8kbyW50SemOCk7JP0wEbmYpHNiW1wKT6ICuFaOK742R/w0vTQ==} - '@cloudflare/kv-asset-handler@0.3.0': - resolution: {integrity: sha512-9CB/MKf/wdvbfkUdfrj+OkEwZ5b7rws0eogJ4293h+7b6KX5toPwym+VQKmILafNB9YiehqY0DlNrDcDhdWHSQ==} - '@colors/colors@1.5.0': resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} @@ -635,6 +586,7 @@ packages: '@dotlottie/player-component@1.3.2': resolution: {integrity: sha512-TKjLNEcaFURMiedRniiCq1hM69Jx6dnE0KXbrW/pcrfLfWM2ewKGumRwYB7aM2+hY6fYIXqkH/CidiBSTg8GAQ==} + deprecated: This package is superceded by @lottiefiles/dotlottie-wc '@emnapi/runtime@0.45.0': resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==} @@ -645,138 +597,420 @@ packages: peerDependencies: cosmiconfig: '>=6' + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.23.1': + resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.17.18': resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.23.1': + resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.17.18': resolution: {integrity: sha512-EmwL+vUBZJ7mhFCs5lA4ZimpUH3WMAoqvOIYhVQwdIgSpHC8ImHdsRyhHAVxpDYUSm0lWvd63z0XH1IlImS2Qw==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.23.1': + resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.17.18': resolution: {integrity: sha512-x+0efYNBF3NPW2Xc5bFOSFW7tTXdAcpfEg2nXmxegm4mJuVeS+i109m/7HMiOQ6M12aVGGFlqJX3RhNdYM2lWg==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.23.1': + resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.17.18': resolution: {integrity: sha512-6tY+djEAdF48M1ONWnQb1C+6LiXrKjmqjzPNPWXhu/GzOHTHX2nh8Mo2ZAmBFg0kIodHhciEgUBtcYCAIjGbjQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.23.1': + resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.17.18': resolution: {integrity: sha512-Qq84ykvLvya3dO49wVC9FFCNUfSrQJLbxhoQk/TE1r6MjHo3sFF2tlJCwMjhkBVq3/ahUisj7+EpRSz0/+8+9A==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.23.1': + resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.17.18': resolution: {integrity: sha512-fw/ZfxfAzuHfaQeMDhbzxp9mc+mHn1Y94VDHFHjGvt2Uxl10mT4CDavHm+/L9KG441t1QdABqkVYwakMUeyLRA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.23.1': + resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.17.18': resolution: {integrity: sha512-FQFbRtTaEi8ZBi/A6kxOC0V0E9B/97vPdYjY9NdawyLd4Qk5VD5g2pbWN2VR1c0xhzcJm74HWpObPszWC+qTew==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.23.1': + resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.17.18': resolution: {integrity: sha512-R7pZvQZFOY2sxUG8P6A21eq6q+eBv7JPQYIybHVf1XkQYC+lT7nDBdC7wWKTrbvMXKRaGudp/dzZCwL/863mZQ==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.23.1': + resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.17.18': resolution: {integrity: sha512-jW+UCM40LzHcouIaqv3e/oRs0JM76JfhHjCavPxMUti7VAPh8CaGSlS7cmyrdpzSk7A+8f0hiedHqr/LMnfijg==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.23.1': + resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.17.18': resolution: {integrity: sha512-ygIMc3I7wxgXIxk6j3V00VlABIjq260i967Cp9BNAk5pOOpIXmd1RFQJQX9Io7KRsthDrQYrtcx7QCof4o3ZoQ==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.23.1': + resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.17.18': resolution: {integrity: sha512-bvPG+MyFs5ZlwYclCG1D744oHk1Pv7j8psF5TfYx7otCVmcJsEXgFEhQkbhNW8otDHL1a2KDINW20cfCgnzgMQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.23.1': + resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.17.18': resolution: {integrity: sha512-oVqckATOAGuiUOa6wr8TXaVPSa+6IwVJrGidmNZS1cZVx0HqkTMkqFGD2HIx9H1RvOwFeWYdaYbdY6B89KUMxA==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.23.1': + resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.17.18': resolution: {integrity: sha512-3dLlQO+b/LnQNxgH4l9rqa2/IwRJVN9u/bK63FhOPB4xqiRqlQAU0qDU3JJuf0BmaH0yytTBdoSBHrb2jqc5qQ==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.23.1': + resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.17.18': resolution: {integrity: sha512-/x7leOyDPjZV3TcsdfrSI107zItVnsX1q2nho7hbbQoKnmoeUWjs+08rKKt4AUXju7+3aRZSsKrJtaRmsdL1xA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.23.1': + resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.17.18': resolution: {integrity: sha512-cX0I8Q9xQkL/6F5zWdYmVf5JSQt+ZfZD2bJudZrWD+4mnUvoZ3TDDXtDX2mUaq6upMFv9FlfIh4Gfun0tbGzuw==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.23.1': + resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.17.18': resolution: {integrity: sha512-66RmRsPlYy4jFl0vG80GcNRdirx4nVWAzJmXkevgphP1qf4dsLQCpSKGM3DUQCojwU1hnepI63gNZdrr02wHUA==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.23.1': + resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-x64@0.17.18': resolution: {integrity: sha512-95IRY7mI2yrkLlTLb1gpDxdC5WLC5mZDi+kA9dmM5XAGxCME0F8i4bYH4jZreaJ6lIZ0B8hTrweqG1fUyW7jbg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.23.1': + resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.23.1': + resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.17.18': resolution: {integrity: sha512-WevVOgcng+8hSZ4Q3BKL3n1xTv5H6Nb53cBrtzzEjDbbnOmucEVcZeGCsCOi9bAOcDYEeBZbD2SJNBxlfP3qiA==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.23.1': + resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.17.18': resolution: {integrity: sha512-Rzf4QfQagnwhQXVBS3BYUlxmEbcV7MY+BH5vfDZekU5eYpcffHSyjU8T0xucKVuOcdCsMo+Ur5wmgQJH2GfNrg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.23.1': + resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.17.18': resolution: {integrity: sha512-Kb3Ko/KKaWhjeAm2YoT/cNZaHaD1Yk/pa3FTsmqo9uFh1D1Rfco7BBLIPdDOozrObj2sahslFuAQGvWbgWldAg==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.23.1': + resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.17.18': resolution: {integrity: sha512-0/xUMIdkVHwkvxfbd5+lfG7mHOf2FRrxNbPiKWg9C4fFrB8H0guClmaM3BFiRUYrznVoyxTIyC/Ou2B7QQSwmw==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.23.1': + resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.17.18': resolution: {integrity: sha512-qU25Ma1I3NqTSHJUOKi9sAH1/Mzuvlke0ioMJRthLXKm7JiSKVwFghlGbDLOO2sARECGhja4xYfRAZNPAkooYg==} engines: {node: '>=12'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.23.1': + resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -920,10 +1154,12 @@ packages: '@humanwhocodes/config-array@0.11.8': resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/config-array@0.5.0': resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -931,6 +1167,7 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} @@ -1048,13 +1285,32 @@ packages: cpu: [x64] os: [win32] - '@ioredis/commands@1.2.0': - resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} + '@inquirer/confirm@5.0.0': + resolution: {integrity: sha512-6QEzj6bZg8atviRIL+pR0tODC854cYSjvZxkyCarr8DVaOJPEyuGys7GmEG3W0Rb8kKSQec7P6okt0sJvNneFw==} + engines: {node: '>=18'} + + '@inquirer/core@10.0.0': + resolution: {integrity: sha512-7dwoKCGvgZGHWTZfOj2KLmbIAIdiXP9NTrwGaTO/XDfKMEmyBahZpnombiG6JDHmiOrmK3GLEJRXrWExXCDLmQ==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.7': + resolution: {integrity: sha512-m+Trk77mp54Zma6xLkLuY+mvanPxlE4A7yNKs2HBiyZ4UkVs28Mv5c/pgWrHeInx+USHeX/WEPzjrWrcJiQgjw==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.0': + resolution: {integrity: sha512-YYykfbw/lefC7yKj7nanzQXILM7r3suIvyFlCcMskc99axmsSewXWkAfXKwMbgxL76iAFVmRwmYdwNZNc8gjog==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + '@jridgewell/gen-mapping@0.1.1': resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==} engines: {node: '>=6.0.0'} @@ -1067,6 +1323,10 @@ packages: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} + '@jridgewell/gen-mapping@0.3.5': + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + '@jridgewell/resolve-uri@3.1.0': resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==} engines: {node: '>=6.0.0'} @@ -1079,6 +1339,10 @@ packages: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} + '@jridgewell/set-array@1.2.1': + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.3': resolution: {integrity: sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==} @@ -1088,12 +1352,18 @@ packages: '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.18': resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} '@jridgewell/trace-mapping@0.3.19': resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} + '@jridgewell/trace-mapping@0.3.25': + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} @@ -1170,10 +1440,6 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} - '@mapbox/node-pre-gyp@1.0.10': - resolution: {integrity: sha512-4ySo4CjzStuprMwk35H5pPbkymjv1SF3jGLj6rAHp/xT/RF7TL7bd9CTm1xDY49K2qF7jmR/g7k+SkLETP6opA==} - hasBin: true - '@mdn/browser-compat-data@3.3.14': resolution: {integrity: sha512-n2RC9d6XatVbWFdHLimzzUJxJ1KY8LdjqrW6YvGPiRmsHkhOUx74/Ct10x5Yo7bC/Jvqx7cDEW8IMPv/+vwEzA==} @@ -1186,14 +1452,14 @@ packages: '@microsoft/tsdoc@0.13.2': resolution: {integrity: sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg==} + '@mswjs/interceptors@0.36.5': + resolution: {integrity: sha512-aQ8WF5zQwOdcxLsxSEk9Jd01GgGb80xxqCaiDDlewhtwqpSm8MOvUHslwPydVirasdW09++NxDNNftm1vLY8yA==} + engines: {node: '>=18'} + '@nearform/heap-profiler@2.0.0': resolution: {integrity: sha512-846CWyq3Ky5rzcl8Z3S+VT3z6GQSlYD1G/dqbtANu29NUHoCO+W7tOZRK6eA6FjLHnNX0DvP1Mrt2oFBPnkxLw==} engines: {node: '>= 12.13.0'} - '@netlify/functions@1.4.0': - resolution: {integrity: sha512-gy7ULTIRroc2/jyFVGx1djCmmBMVisIwrvkqggq5B6iDcInRSy2Tpkm+V5C63hKJVkNRskKWtLQKm9ecCaQTjA==} - engines: {node: '>=8.3.0'} - '@next/env@13.3.0': resolution: {integrity: sha512-AjppRV4uG3No7L1plinoTQETH+j2F10TEnrMfzbTUYwze5sBUPveeeBAPZPm8OkJZ1epq9OyYKhZrvbD6/9HCQ==} @@ -1308,29 +1574,14 @@ packages: resolution: {integrity: sha512-7dqywvVudPSrRCW5nTHpHgeWnbBtz8cFkOuKrecm6ih+oO9ciydhWt6OF7HlqupRRmB8Q/gECVdB9LMfToJbRg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - '@nuxt/devalue@2.0.0': - resolution: {integrity: sha512-YBI/6o2EBz02tdEJRBK8xkt3zvOFOWlLBf7WKYGBsSYSRtjjgrqPe2skp6VLLmKx5WbHHDNcW+6oACaurxGzeA==} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} - '@nuxt/kit@3.4.2': - resolution: {integrity: sha512-bFUpkyG2ZF6RYqiW+tXnWssccHQQqMF4kZJJLP/0eKXf+Fkt/Is0R7IY768jy8ylnyqeMBbmpg4Zv5gSZjfZQw==} - engines: {node: ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} - '@nuxt/schema@3.4.2': - resolution: {integrity: sha512-DXB/fyjrAssFt9KGXyS+ZSfm1A0NYKhEoc01wyz1lGo//oETzUh3MmwE6X3x65NPqDlYZ6Mnj+IdftRRophv5Q==} - engines: {node: ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - - '@nuxt/telemetry@2.2.0': - resolution: {integrity: sha512-Z2UmPkBy5WjxvHKuUcl1X6vKWnIyWSP+9UGde1F+MzzZxYgAQybFud1uL2B3KCowxZdoqT1hd2WklV7EtyCwrQ==} - hasBin: true - - '@nuxt/ui-templates@1.1.1': - resolution: {integrity: sha512-PjVETP7+iZXAs5Q8O4ivl4t6qjWZMZqwiTVogUXHoHGZZcw7GZW3u3tzfYfE1HbzyYJfr236IXqQ02MeR8Fz2w==} - - '@nuxt/vite-builder@3.4.2': - resolution: {integrity: sha512-uLyy0sklOvGqj+yHAxSBE+wxyHvHZmYEfFjx03UEdMbYwpJlhPcqrt0pnWFJAkPWf8ZgpKymr8LNngsyYtNtAA==} - engines: {node: ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - peerDependencies: - vue: ^3.2.47 + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} '@peculiar/asn1-schema@2.3.6': resolution: {integrity: sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==} @@ -1359,6 +1610,9 @@ packages: resolution: {integrity: sha512-Oe6ntvgsMTE3hDIqy6sajqHF+MnzJrOF06qC2QSiUEybLL7cp6tjoKUa32gpd9+KPVl4QyMs3E3nsXrx/Vdnlw==} engines: {node: '>=12'} + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} + '@prisma/prisma-fmt-wasm@3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86': resolution: {integrity: sha512-f0Srf2EasvBRKBlTUrqkW5hp889+SRrRkqu0MgcuTXlgHxVMj7DIo1JNylPb1y/HDwtjLWf6AL+sk+2R891SWA==} @@ -1382,90 +1636,85 @@ packages: '@rgba-image/lanczos@0.1.1': resolution: {integrity: sha512-MSGGU7BZmEbg1xHtNp+StARoN7R38zJnFgSEvSzB710nXsHGEaJt//z2VnPfRQTtKSKUXEnp95JSuqDlXTBrYA==} - '@rollup/plugin-alias@5.0.0': - resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-android-arm-eabi@4.24.0': + resolution: {integrity: sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==} + cpu: [arm] + os: [android] - '@rollup/plugin-commonjs@24.1.0': - resolution: {integrity: sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.68.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-android-arm64@4.24.0': + resolution: {integrity: sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==} + cpu: [arm64] + os: [android] - '@rollup/plugin-inject@5.0.3': - resolution: {integrity: sha512-411QlbL+z2yXpRWFXSmw/teQRMkXcAAC8aYTemc15gwJRpvEVDQwoe+N/HTFD8RFG8+88Bme9DK2V9CVm7hJdA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-darwin-arm64@4.24.0': + resolution: {integrity: sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==} + cpu: [arm64] + os: [darwin] - '@rollup/plugin-json@6.0.0': - resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-darwin-x64@4.24.0': + resolution: {integrity: sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==} + cpu: [x64] + os: [darwin] - '@rollup/plugin-node-resolve@15.0.2': - resolution: {integrity: sha512-Y35fRGUjC3FaurG722uhUuG8YHOJRJQbI6/CkbRkdPotSpDj9NtIN85z1zrcyDcCQIW4qp5mgG72U+gJ0TAFEg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==} + cpu: [arm] + os: [linux] - '@rollup/plugin-replace@5.0.2': - resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==} + cpu: [arm] + os: [linux] - '@rollup/plugin-terser@0.4.1': - resolution: {integrity: sha512-aKS32sw5a7hy+fEXVy+5T95aDIwjpGHCTv833HXVtyKMDoVS7pBr5K3L9hEQoNqbJFjfANPrNpIXlTQ7is00eA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.x || ^3.x - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-linux-arm64-gnu@4.24.0': + resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==} + cpu: [arm64] + os: [linux] - '@rollup/plugin-wasm@6.1.2': - resolution: {integrity: sha512-YdrQ7zfnZ54Y+6raCev3tR1PrhQGxYKSTajGylhyP0oBacouuNo6KcNCk+pYKw9M98jxRWLFFca/udi76IDXzg==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-linux-arm64-musl@4.24.0': + resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==} + cpu: [arm64] + os: [linux] - '@rollup/pluginutils@4.2.1': - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==} + cpu: [ppc64] + os: [linux] - '@rollup/pluginutils@5.0.2': - resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0 - peerDependenciesMeta: - rollup: - optional: true + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.24.0': + resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.24.0': + resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.24.0': + resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.24.0': + resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.24.0': + resolution: {integrity: sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.24.0': + resolution: {integrity: sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==} + cpu: [x64] + os: [win32] '@rushstack/eslint-patch@1.1.0': resolution: {integrity: sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A==} @@ -1529,10 +1778,20 @@ packages: resolution: {integrity: sha512-YSfsswOqWfd+M4bXIhT3hwtAb+IV8+ODwIxwdFR/7jTAPZP1wMVnSlpKnXHAN64HFOiP+Tm3HmKusEZ0+09A0w==} engines: {yarn: '>= 1.3.2'} + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + '@testing-library/dom@8.20.0': resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==} engines: {node: '>=12'} + '@testing-library/user-event@14.5.2': + resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + '@tokenizer/token@0.3.0': resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==} @@ -1568,6 +1827,9 @@ packages: '@types/concat-stream@2.0.0': resolution: {integrity: sha512-t3YCerNM7NTVjLuICZo5gYAXYoDvpuuTceCcFQWcDQz26kxUR5uIWolxbIR5jRNIXpMqhOpW/b8imCR1LEmuJw==} + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} @@ -1586,6 +1848,9 @@ packages: '@types/estree@1.0.0': resolution: {integrity: sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/extend@3.0.1': resolution: {integrity: sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw==} @@ -1598,9 +1863,6 @@ packages: '@types/http-cache-semantics@4.0.1': resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==} - '@types/is-ci@3.0.0': - resolution: {integrity: sha512-Q0Op0hdWbYd1iahB+IFNQcWXFq4O0Q5MwQP7uN0souuQ4rPg1vEYcnIOfr1gY+M+6rc8FGoRaBO1mOOvL29sEQ==} - '@types/is-empty@1.2.1': resolution: {integrity: sha512-a3xgqnFTuNJDm1fjsTjHocYJ40Cz3t8utYpi5GNaxzrJC2HSD08ym+whIL7fNqiqBCdM9bcqD1H/tORWAFXoZw==} @@ -1649,6 +1911,9 @@ packages: '@types/node@18.0.6': resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==} + '@types/node@22.8.7': + resolution: {integrity: sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==} + '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -1676,9 +1941,6 @@ packages: '@types/react@18.0.37': resolution: {integrity: sha512-4yaZZtkRN3ZIQD3KSEwkfcik8s0SWV+82dlJot1AbGYHCzJkWP3ENBY6wYeDRmKZ6HkrgoGAmR2HqdwYGp6OEw==} - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - '@types/responselike@1.0.0': resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} @@ -1688,15 +1950,15 @@ packages: '@types/seedrandom@2.4.30': resolution: {integrity: sha512-AnxLHewubLVzoF/A4qdxBGHCKifw8cY32iro3DQX9TPcetE95zBeVt3jnsvtvAUf1vwzMfwzp4t/L2yqPlnjkQ==} - '@types/semver@6.2.3': - resolution: {integrity: sha512-KQf+QAMWKMrtBMsB8/24w53tEsxllMj6TuA80TT/5igJalLI/zm0L3oXRbIAl4Ohfc85gyHX/jhMwsVkmhLU4A==} - '@types/semver@7.3.13': resolution: {integrity: sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==} '@types/sharp@0.31.1': resolution: {integrity: sha512-5nWwamN9ZFHXaYEincMSuza8nNfOof8nmO+mcI+Agx1uMUk4/pQnNIcix+9rLPXzKrm1pS34+6WRDbDV0Jn7ag==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + '@types/supports-color@8.1.1': resolution: {integrity: sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw==} @@ -1706,6 +1968,9 @@ packages: '@types/text-table@0.2.2': resolution: {integrity: sha512-dGoI5Af7To0R2XE8wJuc6vwlavWARsCh3UKJPjWs1YEqGUqfgBI/j/4GX0yf19/DsDPPf0YAXWAp8psNeIehLg==} + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/trusted-types@2.0.3': resolution: {integrity: sha512-NfQ4gyz38SL8sDNrSixxU2Os1a5xcdFxipAFxYEuLUlvU2uDwS4NUpsImcf1//SlWItCVMMLiylsxbmNMToV/g==} @@ -1876,41 +2141,12 @@ packages: resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@unhead/dom@1.1.26': - resolution: {integrity: sha512-6I8z170OAO19h/AslASN4Xw0hqItQFMKhRJQtplQs1BZ62LsDmNKuqJiYueX39U+IfIvIV3j/q1mQwt9lgMwTw==} - - '@unhead/schema@1.1.26': - resolution: {integrity: sha512-l93zaizm+pu36uMssdtzSC2Y61ncZaBBouZn0pB8rVI14V0hPxeXuSNIuPh2WjAm8wfb8EnCSE3LNguoqTar7g==} - - '@unhead/shared@1.1.26': - resolution: {integrity: sha512-gnUfNrl8w7hQHke9P0au7klcG9bHVOXqbDvya2uARA/8TyxNz87i0uakraO+P6/+zf484dw3b3MYkXq0thK2eg==} - - '@unhead/ssr@1.1.26': - resolution: {integrity: sha512-KYJDGgVNtU2i+NHu17o2zFXqsoLukOFEz81XrWQ8nQdY5+VNjy7IiTLp1dlx3umn1ohZjHySz4LXQCT4zUApSw==} - - '@unhead/vue@1.1.26': - resolution: {integrity: sha512-UpxQ0KGmOoiN+Dg19zto5KTcnGV5chBmgiVJTDqUF4BPfr24vRrR65sZGdMoNV7weuD3AD/K0osk2ru+vXxRrA==} - peerDependencies: - vue: '>=2.7 || >=3' - - '@vercel/nft@0.22.6': - resolution: {integrity: sha512-gTsFnnT4mGxodr4AUlW3/urY+8JKKB452LwF3m477RFUJTAaDmcz2JqFuInzvdybYIeyIv1sSONEJxsxnbQ5JQ==} - engines: {node: '>=14'} - hasBin: true - '@vitejs/plugin-react@4.0.0': resolution: {integrity: sha512-HX0XzMjL3hhOYm+0s95pb0Z7F8O81G7joUHgfDd/9J/ZZf5k4xX6QAMFkKsHFxaHlf6X7GD7+XuaZ66ULiJuhQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: vite: ^4.2.0 - '@vitejs/plugin-vue-jsx@3.0.1': - resolution: {integrity: sha512-+Jb7ggL48FSPS1uhPnJbJwWa9Sr90vQ+d0InW+AhBM22n+cfuYqJZDckBc+W3QSHe1WDvewMZfa4wZOtk5pRgw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.0.0 - vue: ^3.0.0 - '@vitejs/plugin-vue@4.2.0': resolution: {integrity: sha512-hYaXFvEKEwyTmwHq2ft7GGeLBvyYLwTM3E5R1jpvzxg9gO4m5PQcTVvj1wEPKoPL8PAt+KAlxo3gyJWnmwzaWQ==} engines: {node: ^14.18.0 || >=16.0.0} @@ -1918,6 +2154,60 @@ packages: vite: ^4.0.0 vue: ^3.2.25 + '@vitest/browser@2.1.3': + resolution: {integrity: sha512-PQ2kLLc9q8ukJutuuYsynHSr31E78/dtYEvPy4jCHLht1LmITqXTVTqu7THWdZ1kXNGrWwtdMqtt3z2mvSKdIg==} + peerDependencies: + playwright: '*' + safaridriver: '*' + vitest: 2.1.3 + webdriverio: '*' + peerDependenciesMeta: + playwright: + optional: true + safaridriver: + optional: true + webdriverio: + optional: true + + '@vitest/coverage-v8@2.1.3': + resolution: {integrity: sha512-2OJ3c7UPoFSmBZwqD2VEkUw6A/tzPF0LmW0ZZhhB8PFxuc+9IBG/FaSM+RLEenc7ljzFvGN+G0nGQoZnh7sy2A==} + peerDependencies: + '@vitest/browser': 2.1.3 + vitest: 2.1.3 + peerDependenciesMeta: + '@vitest/browser': + optional: true + + '@vitest/expect@2.1.3': + resolution: {integrity: sha512-SNBoPubeCJhZ48agjXruCI57DvxcsivVDdWz+SSsmjTT4QN/DfHk3zB/xKsJqMs26bLZ/pNRLnCf0j679i0uWQ==} + + '@vitest/mocker@2.1.3': + resolution: {integrity: sha512-eSpdY/eJDuOvuTA3ASzCjdithHa+GIF1L4PqtEELl6Qa3XafdMLBpBlZCIUCX2J+Q6sNmjmxtosAG62fK4BlqQ==} + peerDependencies: + '@vitest/spy': 2.1.3 + msw: ^2.3.5 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.3': + resolution: {integrity: sha512-XH1XdtoLZCpqV59KRbPrIhFCOO0hErxrQCMcvnQete3Vibb9UeIOX02uFPfVn3Z9ZXsq78etlfyhnkmIZSzIwQ==} + + '@vitest/runner@2.1.3': + resolution: {integrity: sha512-JGzpWqmFJ4fq5ZKHtVO3Xuy1iF2rHGV4d/pdzgkYHm1+gOzNZtqjvyiaDGJytRyMU54qkxpNzCx+PErzJ1/JqQ==} + + '@vitest/snapshot@2.1.3': + resolution: {integrity: sha512-qWC2mWc7VAXmjAkEKxrScWHWFyCQx/cmiZtuGqMi+WwqQJ2iURsVY4ZfAK6dVo6K2smKRU6l3BPwqEBvhnpQGg==} + + '@vitest/spy@2.1.3': + resolution: {integrity: sha512-Nb2UzbcUswzeSP7JksMDaqsI43Sj5+Kry6ry6jQJT4b5gAK+NS9NED6mDb8FlMRCX8m5guaHCDZmqYMMWRy5nQ==} + + '@vitest/utils@2.1.3': + resolution: {integrity: sha512-xpiVfDSg1RrYT0tX6czgerkpcKFmFOF/gCr30+Mve5V2kewCy4Prn1/NDMSRwaSmT7PRaOF83wu+bEtsY1wrvA==} + '@volar/language-core@2.4.10': resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==} @@ -1927,39 +2217,24 @@ packages: '@volar/typescript@2.4.10': resolution: {integrity: sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==} - '@vue/babel-helper-vue-transform-on@1.0.2': - resolution: {integrity: sha512-hz4R8tS5jMn8lDq6iD+yWL6XNB699pGIVLk7WSJnn1dbpjaazsjZQkieJoRX6gW5zpYSCFqQ7jUquPNY65tQYA==} - - '@vue/babel-plugin-jsx@1.1.1': - resolution: {integrity: sha512-j2uVfZjnB5+zkcbc/zsOc0fSNGCMMjaEXP52wdwdIfn0qjFfEYpYZBFKFg+HHnQeJCVrjOeO0YxgaL7DMrym9w==} - - '@vue/compiler-core@3.2.47': - resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==} - '@vue/compiler-core@3.5.12': resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} - '@vue/compiler-dom@3.2.47': - resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} - '@vue/compiler-dom@3.5.12': resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} '@vue/compiler-sfc@2.7.14': resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} - '@vue/compiler-sfc@3.2.47': - resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==} + '@vue/compiler-sfc@3.5.12': + resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} - '@vue/compiler-ssr@3.2.47': - resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} + '@vue/compiler-ssr@3.5.12': + resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} - '@vue/devtools-api@6.5.0': - resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} - '@vue/language-core@2.1.10': resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} peerDependencies: @@ -1968,25 +2243,19 @@ packages: typescript: optional: true - '@vue/reactivity-transform@3.2.47': - resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==} + '@vue/reactivity@3.5.12': + resolution: {integrity: sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==} - '@vue/reactivity@3.2.47': - resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==} + '@vue/runtime-core@3.5.12': + resolution: {integrity: sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==} - '@vue/runtime-core@3.2.47': - resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==} + '@vue/runtime-dom@3.5.12': + resolution: {integrity: sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==} - '@vue/runtime-dom@3.2.47': - resolution: {integrity: sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==} - - '@vue/server-renderer@3.2.47': - resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==} + '@vue/server-renderer@3.5.12': + resolution: {integrity: sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==} peerDependencies: - vue: 3.2.47 - - '@vue/shared@3.2.47': - resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==} + vue: 3.5.12 '@vue/shared@3.5.12': resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} @@ -2069,10 +2338,6 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - acorn-import-assertions@1.9.0: resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} peerDependencies: @@ -2157,10 +2422,6 @@ packages: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@6.2.0: - resolution: {integrity: sha512-kzRaCqXnpzWs+3z5ABPQiVke+iq0KXkHo8xiWV4RPTi5Yli0l97BEQuhXV1s7+aSU/fu1kUuxgS4MsQ0fRuygw==} - engines: {node: '>=14.16'} - ansi-regex@2.1.1: resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} engines: {node: '>=0.10.0'} @@ -2214,24 +2475,10 @@ packages: aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - arch@2.2.0: - resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==} - - archiver-utils@2.1.0: - resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} - engines: {node: '>= 6'} - - archiver@5.3.1: - resolution: {integrity: sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==} - engines: {node: '>= 10'} - - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - are-we-there-yet@3.0.1: resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -2249,12 +2496,12 @@ packages: aria-query@5.1.3: resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.0: resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - array-flatten@3.0.0: resolution: {integrity: sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==} @@ -2301,6 +2548,10 @@ packages: assert@1.5.0: resolution: {integrity: sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + ast-metadata-inferer@0.7.0: resolution: {integrity: sha512-OkMLzd8xelb3gmnp6ToFvvsHLtS6CbagTkFQvQ+ZYFe3/AIl9iKikNR9G7pY3GfOR/2Xc222hwBjzI7HLkE76Q==} @@ -2315,9 +2566,6 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} - async-sema@3.1.1: - resolution: {integrity: sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==} - async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} @@ -2347,13 +2595,6 @@ packages: resolution: {integrity: sha512-PY1UrXL4NHE7J0hA6GGN2r8xjiAePS/bii3Hz7NOvp4JO3xDNBgRftDjfAxj1t6FDWXiXEOuKF/pdDiisIS8ZA==} hasBin: true - autoprefixer@10.4.14: - resolution: {integrity: sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -2404,28 +2645,18 @@ packages: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} - bindings@1.5.0: - resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} - bit-twiddle@1.0.2: resolution: {integrity: sha512-B9UhK0DKFZhoTFcfvAzhqsjStvGJp9vYWf3+6SNTtdSQnvIgfkHbgHrg/e4+TH71N2GDu8tpmCVoyfrL1d7ntA==} bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - bl@5.1.0: - resolution: {integrity: sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==} - bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} bn.js@5.2.1: resolution: {integrity: sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==} - body-parser@1.20.1: - resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -2447,9 +2678,6 @@ packages: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} - breakword@1.0.5: - resolution: {integrity: sha512-ex5W9DoOQ/LUEU3PMdLs9ua/CYZl1678NUkKOdUSi8Aw5F1idieaiRURCBFJCwVcrD1J8Iy3vfWSloaMwO2qFg==} - brfs@2.0.2: resolution: {integrity: sha512-IrFjVtwu4eTJZyu8w/V2gxU7iLTtcHih67sgEdzrhjLBMHp2uYefUBfdM4k2UvcuWMgV7PQDZHSLeNWnLFKWVQ==} hasBin: true @@ -2517,13 +2745,6 @@ packages: buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} @@ -2533,11 +2754,11 @@ packages: builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} - bundle-require@3.1.2: - resolution: {integrity: sha512-Of6l6JBAxiyQ5axFxUM6dYeP/W7X2Sozeo/4EYB9sJhL+dqL7TKjg+shwxp6jlu/6ZSERfsYtIpSJ1/x3XkAEA==} + bundle-require@5.0.0: + resolution: {integrity: sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: - esbuild: '>=0.13' + esbuild: '>=0.18' busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} @@ -2547,13 +2768,6 @@ packages: resolution: {integrity: sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA==} engines: {node: '>= 0.8'} - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - c12@1.4.1: - resolution: {integrity: sha512-0x7pWfLZpZsgtyotXtuepJc0rZYE0Aw8PwNAXs0jSG9zq6Sl5xmbWnFqfmRY01ieZLHNbvneSFm9/x88CvzAuw==} - cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -2603,9 +2817,6 @@ packages: resolution: {integrity: sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==} engines: {node: '>=14.16'} - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001480: resolution: {integrity: sha512-q7cpoPPvZYgtyC4VaBSN0Bt+PJ4c4EYRf0DrduInOz2SkFpHD5p3LnvEpqBp7UnJn+8x1Ogl1s38saUxe+ihQQ==} @@ -2622,6 +2833,10 @@ packages: cephes@1.2.0: resolution: {integrity: sha512-twuUuJRrIrsELHz6foJtZlqrz6FC36zoHZJvvThsrM1UWPKxyoilw1Rka6Hk0AmPFKHKUoGwGfAtvNZNtNZu0g==} + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} + chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -2656,10 +2871,18 @@ packages: chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} @@ -2679,9 +2902,6 @@ packages: ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - ci-info@3.3.2: - resolution: {integrity: sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==} - ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} @@ -2693,9 +2913,6 @@ packages: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} - clear@0.1.0: - resolution: {integrity: sha512-qMjRnoL+JDPJHeLePZJuao6+8orzHMGP04A8CdwCNsKhRbOnKRjefxONR7bwILT3MHecxKBjHkKL/tkZ8r4Uzw==} - cli-boxes@2.2.1: resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} engines: {node: '>=6'} @@ -2712,10 +2929,6 @@ packages: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} - cli-cursor@4.0.0: - resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cli-spinners@2.7.0: resolution: {integrity: sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==} engines: {node: '>=6'} @@ -2739,8 +2952,8 @@ packages: cli-width@2.2.1: resolution: {integrity: sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==} - cli-width@4.0.0: - resolution: {integrity: sha512-ZksGS2xpa/bYkNzN3BAw1wEjsLV/ZKOf/CCrJ/QOBsxx6fOARIkwTutxp1XIOIohi6HKmOFjMoK/XaqDVUpEEw==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} client-only@0.0.1: @@ -2754,16 +2967,9 @@ packages: clipboard-copy@4.0.1: resolution: {integrity: sha512-wOlqdqziE/NNTUJsfSgXmBMIrYmfd5V0HCGsR8uAKHcg+h9NENWINcfRjtWGU77wDHC8B8ijV4hMTGYbrKovng==} - clipboardy@3.0.0: - resolution: {integrity: sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - cliui@5.0.0: resolution: {integrity: sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==} - cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2778,10 +2984,6 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} - cluster-key-slot@1.1.2: - resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} - engines: {node: '>=0.10.0'} - code-point-at@1.1.0: resolution: {integrity: sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==} engines: {node: '>=0.10.0'} @@ -2810,9 +3012,6 @@ packages: resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} engines: {node: '>=12.5.0'} - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} @@ -2856,16 +3055,9 @@ packages: commist@1.1.0: resolution: {integrity: sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==} - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - compare-func@2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - compress-commons@4.1.1: - resolution: {integrity: sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==} - engines: {node: '>= 10'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -2895,8 +3087,9 @@ packages: confusing-browser-globals@1.0.11: resolution: {integrity: sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==} - consola@3.1.0: - resolution: {integrity: sha512-rrrJE6rP0qzl/Srg+C9x/AE5Kxfux7reVm1Wh0wCjuXvih6DqZgqDZe8auTD28fzJ9TF0mHlSDrPpWlujQRo1Q==} + consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} console-browserify@1.2.0: resolution: {integrity: sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==} @@ -2907,14 +3100,6 @@ packages: constants-browserify@1.0.0: resolution: {integrity: sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==} - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - conventional-changelog-angular@5.0.13: resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} engines: {node: '>=10'} @@ -2937,12 +3122,6 @@ packages: convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - cookie-es@0.5.0: - resolution: {integrity: sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==} - - cookie-signature@1.0.6: - resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.5.0: resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} engines: {node: '>= 0.6'} @@ -2991,15 +3170,6 @@ packages: typescript: optional: true - crc-32@1.2.2: - resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} - engines: {node: '>=0.8'} - hasBin: true - - crc32-stream@4.0.2: - resolution: {integrity: sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==} - engines: {node: '>= 10'} - create-ecdh@4.0.4: resolution: {integrity: sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==} @@ -3047,86 +3217,26 @@ packages: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} - css-declaration-sorter@6.4.0: - resolution: {integrity: sha512-jDfsatwWMWN0MODAFuHszfjphEXfNw9JUAhmY4pLu3TyTU+ohUpsbVtbU+1MZn4a47D9kqh03i4eyOm+74+zew==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - css-select@5.1.0: - resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} - css-tree@1.1.3: resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} engines: {node: '>=8.0.0'} - css-tree@2.2.1: - resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - css-tree@2.3.1: - resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - css-what@6.1.0: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@6.0.0: - resolution: {integrity: sha512-BDxlaFzObRDXUiCCBQUNQcI+f1/aX2mgoNtXGjV6PG64POcHoDUoX+LgMWw+Q4609QhxwkcSnS65YFs42RA6qQ==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano-utils@4.0.0: - resolution: {integrity: sha512-Z39TLP+1E0KUcd7LGyF4qMfu8ZufI0rDzhdyAMsa/8UyNUU8wpS0fhdBxbQbv32r64ea00h4878gommRVg2BHw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano@6.0.0: - resolution: {integrity: sha512-RGlcbzGhzEBCHuQe3k+Udyj5M00z0pm9S+VurHXFEOXxH+y0sVrJH2sMzoyz2d8N1EScazg+DVvmgyx0lurwwA==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - csso@4.2.0: resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} engines: {node: '>=8.0.0'} - csso@5.0.5: - resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} - engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - - csstype@2.6.21: - resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} - csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - csv-generate@3.4.3: - resolution: {integrity: sha512-w/T+rqR0vwvHqWs/1ZyMDWtHHSJaN06klRqJXBEpDJaM/+dZkso0OKh1VcuuYvK3XM53KysVNq8Ko/epCK8wOw==} - - csv-parse@4.16.3: - resolution: {integrity: sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==} - - csv-stringify@5.6.5: - resolution: {integrity: sha512-PjiQ659aQ+fUTQqSrd1XEDnOr52jh30RBurfzkscaE2tPaFsDH5wOAHJiw8XAHphRknCwMUE9KRayc4K/NbO8A==} - - csv@5.5.3: - resolution: {integrity: sha512-QTaY0XjjhTQOdguARF0lGKm5/mEq9PD9/VhZZegHDIBq2tQwgNpHc3dneD4mGo2iJs+fTKv5Bp0fZ+BRuY3Z0g==} - engines: {node: '>= 0.1.90'} - - cuint@0.2.2: - resolution: {integrity: sha512-d4ZVpCW31eWwCMe1YT3ur7mUDnTXbgwyzaL320DrcRT45rfjYxkt5QWLrmOJ+/UEAI2+fQgKe/fCjR8l4TpRgw==} + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} cwise-compiler@1.1.3: resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==} @@ -3439,6 +3549,15 @@ packages: supports-color: optional: true + debug@4.3.7: + resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decamelize-keys@1.1.0: resolution: {integrity: sha512-ocLWuYzRPoS9bfiSdDd3cxvrzovVMZnRDVEzAs+hWIVXGDbHxWMECij2OBuyB/An0FFW/nLuq6Kv1i/YC5Qfzg==} engines: {node: '>=0.10.0'} @@ -3461,6 +3580,10 @@ packages: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + deep-equal@2.2.1: resolution: {integrity: sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==} @@ -3471,10 +3594,6 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - defaults@1.0.3: resolution: {integrity: sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==} @@ -3485,10 +3604,6 @@ packages: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} - define-lazy-prop@2.0.0: - resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} - engines: {node: '>=8'} - define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -3496,9 +3611,6 @@ packages: defined@1.0.1: resolution: {integrity: sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==} - defu@6.1.2: - resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} - degenerator@5.0.1: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} @@ -3513,18 +3625,10 @@ packages: delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - denque@2.1.0: - resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} - engines: {node: '>=0.10'} - depd@1.1.2: resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} engines: {node: '>= 0.6'} - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - deps-sort@2.0.1: resolution: {integrity: sha512-1orqXQr5po+3KI6kQb9A4jnXT1PBwggGl2d7Sq2xsnOeI9GPcE/tGcF9UiSZtZBM7MukY4cAh7MemS6tZYipfw==} hasBin: true @@ -3536,21 +3640,10 @@ packages: des.js@1.0.1: resolution: {integrity: sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==} - destr@1.2.2: - resolution: {integrity: sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-indent@6.1.0: resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} engines: {node: '>=8'} - detect-libc@2.0.1: - resolution: {integrity: sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w==} - engines: {node: '>=8'} - detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -3560,9 +3653,6 @@ packages: engines: {node: '>=0.8.0'} hasBin: true - devalue@4.3.0: - resolution: {integrity: sha512-n94yQo4LI3w7erwf84mhRUkUJfhLoCZiLyoOZ/QFsDbcWNZePrLwbQpvZBUG2TNxwV3VjCKPxkiiQA6pe3TrTA==} - devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -3604,9 +3694,6 @@ packages: dom-serializer@1.4.1: resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} - domain-browser@1.2.0: resolution: {integrity: sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==} engines: {node: '>=0.4', npm: '>=1.2'} @@ -3618,19 +3705,12 @@ packages: resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} engines: {node: '>= 4'} - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - dompurify@2.3.5: resolution: {integrity: sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - domutils@3.0.1: - resolution: {integrity: sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==} - dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -3639,14 +3719,6 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} - dot-prop@7.2.0: - resolution: {integrity: sha512-Ol/IPXUARn9CSbkrdV4VJo7uCy1I3VuSiWCaFSg+8BdUOzF9n3jefIpcgAydvUZbTdEBZs2vEiTiS9m61ssiDA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - dotenv@16.0.3: - resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==} - engines: {node: '>=12'} - dset@3.1.2: resolution: {integrity: sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==} engines: {node: '>=4'} @@ -3672,14 +3744,6 @@ packages: ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - ejs@3.1.9: - resolution: {integrity: sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==} - engines: {node: '>=0.10.0'} - hasBin: true - electron-to-chromium@1.4.371: resolution: {integrity: sha512-jlBzY4tFcJaiUjzhRTCWAqRvTO/fWzjA3Bls0mykzGZ7zvcMP7h05W6UcgzfT9Ca1SW2xyKDOFRyI0pQeRNZGw==} @@ -3695,10 +3759,6 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - encodeurl@1.0.2: - resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} - engines: {node: '>= 0.8'} - encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} @@ -3708,14 +3768,6 @@ packages: endpoint@0.4.5: resolution: {integrity: sha512-oA2ALUF+d4Y0I8/WMV/0BuAZGHxfIdAygr9ZXP4rfzmp5zpYZmYKHKAbqRQnrE1YGdPhVg4D24CQkyx2qYEoHg==} - enhanced-resolve@4.5.0: - resolution: {integrity: sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==} - engines: {node: '>=6.9.0'} - - enhanced-resolve@5.13.0: - resolution: {integrity: sha512-eyV8f0y1+bzyfh8xAwW/WTSZpLbjhqc4ne9eGSH4Zo2ejdyiNG9pU6mf9DG8a7+Auk6MFTlNOT4Y2y/9k8GKVg==} - engines: {node: '>=10.13.0'} - enhanced-resolve@5.15.0: resolution: {integrity: sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==} engines: {node: '>=10.13.0'} @@ -3745,10 +3797,6 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - errno@0.1.8: - resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} - hasBin: true - error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -3923,6 +3971,16 @@ packages: engines: {node: '>=12'} hasBin: true + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.23.1: + resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -3935,9 +3993,6 @@ packages: resolution: {integrity: sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==} engines: {node: '>=12'} - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -4193,11 +4248,13 @@ packages: eslint@7.32.0: resolution: {integrity: sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==} engines: {node: ^10.12.0 || >=12.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.39.0: resolution: {integrity: sha512-mwiok6cy7KTW7rBpo05k6+p4YVZByLNjAZ/ACB9DRCu4YDRwjXI01tWHp6KAUWelsBetTxKK/2sHB0vdS8Z2Og==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@7.3.1: @@ -4256,10 +4313,6 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - event-emitter@0.3.5: resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==} @@ -4270,9 +4323,6 @@ packages: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -4292,20 +4342,12 @@ packages: resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - execa@7.1.1: - resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} - execspawn@1.0.1: resolution: {integrity: sha512-s2k06Jy9i8CUkYe0+DxRlvtkZoOkwwfhB+Xxo5HGUtrISVW2m98jO2tr67DGRFxZwkjQqloA3v/tNtjhBRBieg==} expect-more@1.2.0: resolution: {integrity: sha512-AVnjc5oh2jgiJjOrjbiKxbwLlNA/zsl2044Nbd09H4+2KwThtSLYKhdOusLYOrcToFAa2uBOWR1ExCN4kOWgbQ==} - express@4.18.2: - resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} - engines: {node: '>= 0.10.0'} - ext@1.7.0: resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==} @@ -4319,9 +4361,6 @@ packages: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} - externality@1.0.0: - resolution: {integrity: sha512-MAU9ci3XdpqOX1aoIoyL2DMzW97P8LYeJxIUkfXhOfsrkH4KLHFaYDwKN0B2l6tqedVJWiTIJtWmxmZfa05vOQ==} - extract-files@11.0.0: resolution: {integrity: sha512-FuoE1qtbJ4bBVvv94CC7s0oTnKUGvQs+Rjf1L2SJFfS+HTVVjhPFtehPdQ0JiGPqVNfSSZvL5yzHHQq2Z4WNhQ==} engines: {node: ^12.20 || >= 14.13} @@ -4378,6 +4417,14 @@ packages: fd-slicer@1.1.0: resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + fdir@6.4.2: + resolution: {integrity: sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + fetch-blob@3.2.0: resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} engines: {node: ^12.20 || >= 14.13} @@ -4387,15 +4434,12 @@ packages: figgy-pudding@3.5.2: resolution: {integrity: sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==} + deprecated: This module is no longer supported. figures@2.0.0: resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} engines: {node: '>=4'} - figures@5.0.0: - resolution: {integrity: sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==} - engines: {node: '>=14'} - file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4404,20 +4448,10 @@ packages: resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} engines: {node: '>=18'} - file-uri-to-path@1.0.0: - resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} - - filelist@1.0.4: - resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==} - fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - find-chrome-bin@0.1.0: resolution: {integrity: sha512-XoFZwaEn1R3pE6zNG8kH64l2e093hgB9+78eEKPmJK0o1EXEou+25cEWdtu2qq4DBQPDSe90VJAWVI2Sz9pX6Q==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -4434,9 +4468,6 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-yarn-workspace-root2@1.2.16: - resolution: {integrity: sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==} - flame-gradient@1.0.0: resolution: {integrity: sha512-9ejk16/DqvQJ4dHsh68W/4N0zmVQ60zukyUuEHrTbf5pJvP4JqlIdke86Z9174PZokRCXAntY5+H1txSyC7mUA==} @@ -4444,30 +4475,17 @@ packages: resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==} engines: {node: ^10.12.0 || >=12.0.0} - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true - flatstr@1.0.12: resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==} flatted@3.2.7: resolution: {integrity: sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==} - follow-redirects@1.15.2: - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - foreground-child@3.1.1: - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} forever-agent@0.6.1: @@ -4493,10 +4511,6 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - fp-and-or@0.1.3: resolution: {integrity: sha512-wJaE62fLaB3jCYvY2ZHjZvmKK2iiLiiehX38rz5QZxtdN8fVPJDeZUiVvJrHStdTc+23LHlyZuSEKgFc0pxi2g==} engines: {node: '>=10'} @@ -4504,13 +4518,6 @@ packages: fp-ts@2.12.1: resolution: {integrity: sha512-oxvgqUYR6O9VkKXrxkJ0NOyU0FrE705MeqgBUMEPWyTu6Pwn768cJbHChw2XOBlgFLKfIHxjr2OOBFpv2mUGZw==} - fraction.js@4.2.0: - resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - from2-string@1.1.0: resolution: {integrity: sha512-m8vCh+KnXXXBtfF2VUbiYlQ+nczLcntB0BrtNgpmLkHylhObe9WF1b2LZjBBzrZzA6P4mkEla6ZYQoOUTG8cYA==} @@ -4550,6 +4557,11 @@ packages: fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4568,13 +4580,10 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - gauge@4.0.4: resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -4596,9 +4605,6 @@ packages: get-intrinsic@1.2.0: resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==} - get-port-please@3.0.1: - resolution: {integrity: sha512-R5pcVO8Z1+pVDu8Ml3xaJCEkBiiy1VQN9za0YqH8GIi1nIqD4IzQhzY6dDzMRtdS1lyiGlucRzm8IN8wtLIXng==} - get-stdin@8.0.0: resolution: {integrity: sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==} engines: {node: '>=10'} @@ -4630,25 +4636,11 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - giget@1.1.2: - resolution: {integrity: sha512-HsLoS07HiQ5oqvObOI+Qb2tyZH4Gj5nYGfF9qQcZNrPw+uEFhdXtgJr01aO2pWadGHucajYDLxxbtQkm97ON2A==} - hasBin: true - - git-config-path@2.0.0: - resolution: {integrity: sha512-qc8h1KIQbJpp+241id3GuAtkdyJ+IK+LIVtkiFTRKRrmddDzs3SI9CvP1QYmWBFvm1I/PWRwj//of8bgAc0ltA==} - engines: {node: '>=4'} - git-raw-commits@2.0.11: resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} engines: {node: '>=10'} hasBin: true - git-up@7.0.0: - resolution: {integrity: sha512-ONdIrbBCFusq1Oy0sC71F5azx8bVkvtZtMJAsv+a6lz5YAmbNnLD6HAB4gptHZVLPR8S2/kVN6Gab7lryq5+lQ==} - - git-url-parse@13.1.0: - resolution: {integrity: sha512-5FvPJP/70WkIprlUZ33bm4UAaFdjcLkJLpWft1BeZKqwR0uhhNGoKwlUaPtVb4LxCSQ++erHapRak9kWGj+FCA==} - github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} @@ -4666,27 +4658,27 @@ packages: glob-to-regexp@0.4.1: resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} - glob@10.3.4: - resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} - engines: {node: '>=16 || 14 >=14.17'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true - glob@7.1.6: - resolution: {integrity: sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==} - glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Glob versions prior to v9 are no longer supported glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.0.3: resolution: {integrity: sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-dirs@0.1.1: resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==} @@ -4761,13 +4753,6 @@ packages: resolution: {integrity: sha512-59LZHPdGZVh695Ud9lRzPBVTtlX9ZCV150Er2W43ro37wVof0ctenSaskPPjN7lVTIN8mSZt8PHUNKZuNQUuxw==} engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} - gzip-size@7.0.0: - resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - h3@1.6.4: - resolution: {integrity: sha512-uoDNeaoeDRwWBtwwi4siZ6l5sBmDJpnpcBssuAbvsaPBonl8vP7Ym4tFPe+tAvGM0GbUoC24wYcloCG+J9hqmA==} - har-schema@2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} engines: {node: '>=4'} @@ -4833,9 +4818,6 @@ packages: resolution: {integrity: sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==} engines: {node: '>=4'} - hash-sum@2.0.0: - resolution: {integrity: sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==} - hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} @@ -4859,6 +4841,9 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + hidden-markov-model-tf@4.0.0: resolution: {integrity: sha512-q8VeBNCyQ5CNsUlbt4T5JXc+pUeKqq7LEGjs4HiH+thgZ2fuyJ9pf/V66ZFx9jZobXkwxVuQRWKZa3TwOFW+zw==} peerDependencies: @@ -4867,9 +4852,6 @@ packages: hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} - hookable@5.5.3: - resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} - hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -4884,9 +4866,8 @@ packages: hsl-to-rgb-for-reals@1.1.1: resolution: {integrity: sha512-LgOWAkrN0rFaQpfdWBQlv/VhkOxb5AsBjk6NQVx4yEzWS923T07X0M1Y0VNko2H52HeSpZrZNNMJ0aFqsdVzQg==} - html-tags@3.3.1: - resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} - engines: {node: '>=8'} + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} htmlescape@1.1.1: resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} @@ -4898,10 +4879,6 @@ packages: http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - http-errors@2.0.0: - resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} - engines: {node: '>= 0.8'} - http-parser-js@0.5.8: resolution: {integrity: sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==} @@ -4913,14 +4890,6 @@ packages: resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} engines: {node: '>= 14'} - http-proxy@1.18.1: - resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} - engines: {node: '>=8.0.0'} - - http-shutdown@1.2.2: - resolution: {integrity: sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw==} - engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - http-signature@1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} @@ -4955,10 +4924,6 @@ packages: resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==} engines: {node: '>=12.20.0'} - human-signals@4.3.1: - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} - engines: {node: '>=14.18.0'} - humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} @@ -5036,6 +5001,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.1: resolution: {integrity: sha512-8nWq2nLTAwd02jTqJExUYFSD/fKq6VH9Y/oG2accc/kdI0V98Bag8d5a4gi3XHz73rDWa2PvTtvcWYquKqSENA==} @@ -5061,10 +5027,6 @@ packages: resolution: {integrity: sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==} engines: {node: '>=6.0.0'} - inquirer@9.2.0: - resolution: {integrity: sha512-WWERbVqjsTXjXub1ZW0ZHDit1dyHqy0T9XIkky9TnmKAPrjU9Jkd59nZPK0dUuM3s73GZAZu2Jo4iFU3XSPVLA==} - engines: {node: '>=14.18.0'} - insert-module-globals@7.2.1: resolution: {integrity: sha512-ufS5Qq9RZN+Bu899eA9QCAYThY+gGW7oRkmb0vC93Vlyu/CFGcH0OYPEjVkDXA5FEbTt1+VWzdoOD3Ny9N+8tg==} hasBin: true @@ -5084,30 +5046,15 @@ packages: internmap@1.0.1: resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} - ioredis@5.3.2: - resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==} - engines: {node: '>=12.22.0'} - iota-array@1.0.0: resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==} - ip-regex@5.0.0: - resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - ip@1.1.8: resolution: {integrity: sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==} ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - iron-webcrypto@0.6.0: - resolution: {integrity: sha512-WYgEQttulX/+JTv1BTJFYY3OsAb+ZnCuA53IjppZMyiRsVdGeEuZ/k4fJrg77Rzn0pp9/PgWtXUF+5HndDA5SQ==} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -5151,10 +5098,6 @@ packages: resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} engines: {node: '>=4'} - is-builtin-module@3.2.1: - resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} - engines: {node: '>=6'} - is-callable@1.2.7: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} @@ -5182,11 +5125,6 @@ packages: engines: {node: '>=8'} hasBin: true - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - is-empty@1.2.0: resolution: {integrity: sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==} @@ -5229,23 +5167,19 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - is-negative-zero@2.0.2: resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} engines: {node: '>= 0.4'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-npm@5.0.0: resolution: {integrity: sha512-WW/rQLOazUq+ST/bCAVBp/2oMERWLsR7OrKyt052dNDk4DHcDE0/7QSXITlmi+VBcV13DfIbysG3tZJm5RfdBA==} engines: {node: '>=10'} @@ -5282,19 +5216,9 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-primitive@3.0.1: - resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} - engines: {node: '>=0.10.0'} - - is-promise@4.0.0: - resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - is-regex@1.1.4: resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} engines: {node: '>= 0.4'} @@ -5305,9 +5229,6 @@ packages: is-shared-array-buffer@1.0.2: resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} - is-ssh@1.4.0: - resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -5347,10 +5268,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - is-url@1.2.4: resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} @@ -5399,36 +5316,29 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} - jackspeak@2.3.1: - resolution: {integrity: sha512-4iSY3Bh1Htv+kLhiiZunUhQ+OYXIn0ze3ulq8JeWrFKmhPAJSySV2+kdtRh2pGcCeF0s6oR8Oc+pYZynJj4t8A==} - engines: {node: '>=14'} + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} - jake@10.8.7: - resolution: {integrity: sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==} + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - hasBin: true - jasmine-browser-runner@2.2.0: - resolution: {integrity: sha512-EKHp7sUsbKM42qlq44kx497IyQRmAXaNhrVogPAzIEs5LuKXhwSI7DZEjwr22o4rZf4T5pWDcLb3/WtB08f9Lg==} - hasBin: true - peerDependencies: - jasmine-core: ^5.0.0 + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} - jasmine-core@5.1.1: - resolution: {integrity: sha512-UrzO3fL7nnxlQXlvTynNAenL+21oUQRlzqQFsA2U11ryb4+NLOCOePZ70PTojEaUKhiFugh7dG0Q+I58xlPdWg==} + istanbul-reports@3.1.7: + resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==} + engines: {node: '>=8'} - jasmine@5.1.0: - resolution: {integrity: sha512-prmJlC1dbLhti4nE4XAPDWmfJesYO15sjGXVp7Cs7Ym5I9Xtwa/hUHxxJXjnpfLO72+ySttA0Ztf8g/RiVnUKw==} - hasBin: true + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} jest-worker@27.5.1: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} - jiti@1.18.2: - resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} - hasBin: true - jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -5504,9 +5414,6 @@ packages: engines: {node: '>=6'} hasBin: true - jsonc-parser@3.2.0: - resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -5553,21 +5460,10 @@ packages: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - kleur@4.1.5: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} - klona@2.0.6: - resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} - engines: {node: '>= 8'} - - knitwork@1.0.0: - resolution: {integrity: sha512-dWl0Dbjm6Xm+kDxhPQJsCBTxrJzuGl0aP9rhr+TG8D3l+GL90N8O8lYUi7dTSAN2uuDqCtNgb6aEuQH5wsiV8Q==} - labeled-stream-splicer@2.0.2: resolution: {integrity: sha512-Ca4LSXFFZUjPScRaqOcFxneA0VpKZr4MMYCljyQr4LIewTLb3Y0IUTIsnBBsVubIeEfxeSZpSjSsRM8APEQaAw==} @@ -5585,10 +5481,6 @@ packages: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} - lazystream@1.0.1: - resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==} - engines: {node: '>= 0.6.3'} - leven@2.1.0: resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} engines: {node: '>=0.10.0'} @@ -5620,6 +5512,10 @@ packages: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -5632,9 +5528,6 @@ packages: engines: {node: ^14.13.1 || >=16.0.0} hasBin: true - listhen@1.0.4: - resolution: {integrity: sha512-r94k7kmXHb8e8wpv7+UP/qqhhD+j/9TgX19QKim2cEJuWCLwlTw+5BkCFmYyjhQ7Bt8KdVun/2DcD7MF2Fe3+g==} - listr2@4.0.5: resolution: {integrity: sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==} engines: {node: '>=12'} @@ -5663,18 +5556,10 @@ packages: resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - load-yaml-file@0.2.0: - resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} - engines: {node: '>=6'} - loader-runner@4.3.0: resolution: {integrity: sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==} engines: {node: '>=6.11.5'} - local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} - engines: {node: '>=14'} - locate-path@3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} @@ -5687,9 +5572,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash._reinterpolate@3.0.0: - resolution: {integrity: sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==} - lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} @@ -5702,21 +5584,12 @@ packages: lodash.debounce@4.0.8: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - lodash.defaults@4.2.0: - resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} - - lodash.difference@4.5.0: - resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==} - lodash.flatten@4.4.0: resolution: {integrity: sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==} lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} - lodash.isarguments@3.1.0: - resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==} - lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} @@ -5738,9 +5611,6 @@ packages: lodash.mergewith@4.6.2: resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==} - lodash.pick@4.4.0: - resolution: {integrity: sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==} - lodash.snakecase@4.1.1: resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==} @@ -5750,18 +5620,9 @@ packages: lodash.startcase@4.4.0: resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==} - lodash.template@4.5.0: - resolution: {integrity: sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==} - - lodash.templatesettings@4.2.0: - resolution: {integrity: sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==} - lodash.truncate@4.4.2: resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} - lodash.union@4.6.0: - resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} - lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} @@ -5775,10 +5636,6 @@ packages: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} - log-symbols@5.1.0: - resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} - engines: {node: '>=12'} - log-update@4.0.0: resolution: {integrity: sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==} engines: {node: '>=10'} @@ -5796,6 +5653,9 @@ packages: lottie-web@5.11.0: resolution: {integrity: sha512-9vSt0AtdOH98GKDXwD5LPfFg9Pcmxt5+1BllAbudKM5iqPxpJnJUfuGaP45OyudDrESCOBgsjnntVUTygBNlzw==} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + lower-case@1.1.4: resolution: {integrity: sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==} @@ -5811,9 +5671,8 @@ packages: resolution: {integrity: sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - lru-cache@10.0.1: - resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} - engines: {node: 14 || >=16.14} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -5829,10 +5688,6 @@ packages: resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} engines: {node: '>=12'} - lru-cache@9.1.1: - resolution: {integrity: sha512-65/Jky17UwSb0BuB9V+MyDpsOtXKmYwzhyl+cOa9XUiI4uV2Ouy/2voFP3+al0BjZbJgMBD8FojMpAf+Z+qn4A==} - engines: {node: 14 || >=16.14} - lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -5847,21 +5702,20 @@ packages: magic-string@0.25.1: resolution: {integrity: sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==} - magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - - magic-string@0.27.0: - resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} - engines: {node: '>=12'} + magic-string@0.30.12: + resolution: {integrity: sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==} - magic-string@0.30.0: - resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==} - engines: {node: '>=12'} + magicast@0.3.5: + resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -5974,31 +5828,10 @@ packages: mdn-data@2.0.14: resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - mdn-data@2.0.28: - resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} - - mdn-data@2.0.30: - resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - memory-fs@0.5.0: - resolution: {integrity: sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==} - engines: {node: '>=4.3.0 <5.0.0 || >=5.10'} - - meow@6.1.1: - resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} - engines: {node: '>=8'} - meow@8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - merge-source-map@1.0.4: resolution: {integrity: sha512-PGSmS0kfnTnMJCzJ16BLLCEe6oeYCamKFFdQKshi4BmM6FUwipjVOcBFGxqtQtirtAG4iZvHlqST9CpZKqlRjA==} @@ -6021,10 +5854,6 @@ packages: '@types/node': optional: true - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - micromark-core-commonmark@1.0.6: resolution: {integrity: sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA==} @@ -6215,21 +6044,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mime@2.5.2: - resolution: {integrity: sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==} - engines: {node: '>=4.0.0'} - hasBin: true - - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - mimic-fn@1.2.0: resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} engines: {node: '>=4'} @@ -6290,8 +6104,8 @@ packages: resolution: {integrity: sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==} engines: {node: '>=10'} - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} minimist-options@4.1.0: @@ -6335,8 +6149,8 @@ packages: resolution: {integrity: sha512-lwycX3cBMTvcejsHITUgYj6Gy6A7Nh4Q6h9NP4sTHY1ccJlC7yKzDmiShEHsJ16Jf1nKGDEaiHxiltsJEvk0nQ==} engines: {node: '>=8'} - minipass@7.0.3: - resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} minizlib@2.1.2: @@ -6346,10 +6160,6 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} - mixme@0.5.5: - resolution: {integrity: sha512-/6IupbRx32s7jjEwHcycXikJwFD5UujbVNuJFkeKLYje+92OvtuPniF6JhnFm5JCTDUhS+kYK3W/4BWYQYXz7w==} - engines: {node: '>= 8.0.0'} - mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} @@ -6385,9 +6195,6 @@ packages: ml-xsadd@2.0.0: resolution: {integrity: sha512-VoAYUqmPRmzKbbqRejjqceGFp3VF81Qe8XXFGU0UXLxB7Mf4GGvyGq5Qn3k4AiQgDEV6WzobqlPOd+j0+m6IrA==} - mlly@1.2.0: - resolution: {integrity: sha512-+c7A3CV0KGdKcylsI6khWyts/CYrGTrRVo4R/I7u/cUsy0Conxa6LUhiEzVKIw14lc2L5aiO4+SeVe4TeGRKww==} - module-deps@6.2.3: resolution: {integrity: sha512-fg7OZaQBcL4/L+AK5f4iVqf9OMbCclXfy/znXRxTVhJSeW5AIlS9AwheYwDaXM3lVW7OBeaeUEY3gbaC6cLlSA==} engines: {node: '>= 0.8.0'} @@ -6403,6 +6210,10 @@ packages: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} + mrmime@2.0.0: + resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} + engines: {node: '>=10'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -6412,6 +6223,16 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.5.1: + resolution: {integrity: sha512-V0BmHvFkbWGXqbyrc+XiuQ8DU3qzcb6lb8gB9Vzltp3cgHLHLCDF/KmmFo0xw58StNaRMTebw3/xpWVvU9xq9g==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: '>= 4.8.x' + peerDependenciesMeta: + typescript: + optional: true + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -6421,9 +6242,9 @@ packages: mute-stream@0.0.7: resolution: {integrity: sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==} - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} mutexify@1.4.0: resolution: {integrity: sha512-pbYSsOrSB/AKN5h/WzzLRMFgZhClWccf2XIB4RSMC8JbquiB0e0/SH5AIfdQMdyHmYtv4seU7yV/TvAwPLJ1Yg==} @@ -6449,9 +6270,9 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@4.0.2: - resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==} - engines: {node: ^14 || ^16 || >=18} + nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true nanospinner@1.1.0: @@ -6525,11 +6346,6 @@ packages: sass: optional: true - nitropack@2.3.3: - resolution: {integrity: sha512-1g/4zdwWo+tWSvno57rhRXeGk6jNbG5W1yRNtOywInT1nyoEG1ksOwQ3W3JHGB2E1GNjZwAVi611UVOVL+JgYw==} - engines: {node: ^14.16.0 || ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - hasBin: true - no-case@2.3.2: resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==} @@ -6537,9 +6353,6 @@ packages: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} - node-fetch-native@1.1.0: - resolution: {integrity: sha512-nl5goFCig93JZ9FIV8GHT9xpNqXbxQUzkOmKIMKmncsBH9jhg7qKex8hirpymkBFmNQ114chEEG5lS4wgK2I+Q==} - node-fetch@2.6.7: resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==} engines: {node: 4.x || >=6.0.0} @@ -6571,14 +6384,6 @@ packages: resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} - engines: {node: '>= 6.13.0'} - - node-gyp-build@4.6.0: - resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} - hasBin: true - node-gyp@9.3.1: resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} engines: {node: ^12.13 || ^14.13 || >=16} @@ -6596,11 +6401,6 @@ packages: resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==} hasBin: true - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - nopt@6.0.0: resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -6634,10 +6434,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - normalize-url@4.5.1: resolution: {integrity: sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==} engines: {node: '>=8'} @@ -6698,12 +6494,10 @@ packages: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - npmlog@6.0.2: resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -6712,26 +6506,6 @@ packages: resolution: {integrity: sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==} engines: {node: '>=0.10.0'} - nuxi@3.4.2: - resolution: {integrity: sha512-kwKEbfS3EhiQX8BMwcAPgfWiFlV8gBa2dI0kPNYD3Egab5Vixs3P2h6dGq7RsEYZEJ6aU876J44ySF7l8bmDGg==} - engines: {node: ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - hasBin: true - - nuxt@3.4.2: - resolution: {integrity: sha512-4v+oeBL4ZI8nHzF0Dm1p5kF9VCNlzrpvOt7wu3BnmzlueXsu4A/LfmFvpfZLxws+r5U74eM5Ut/LMD8B8LrZIw==} - engines: {node: ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - hasBin: true - peerDependencies: - '@parcel/watcher': ^2.1.0 - '@types/node': ^14.18.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@parcel/watcher': - optional: true - - nypm@0.2.0: - resolution: {integrity: sha512-auBv78LkHyU9TywBE91N+RTkanVyFLsVayZaHW+YYvJDJ3u2PCwLaYB3eecPQD9tgCIXGuH871HlHTdKSf6rtw==} - engines: {node: ^14.16.0 || ^16.10.0 || ^17.0.0 || ^18.0.0 || ^19.0.0} - oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} @@ -6772,16 +6546,6 @@ packages: resolution: {integrity: sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==} engines: {node: '>= 0.4'} - ofetch@1.0.1: - resolution: {integrity: sha512-icBz2JYfEpt+wZz1FRoGcrMigjNKjzvufE26m9+yUiacRQRHwnNlGRPiDnW4op7WX/MR6aniwS8xw8jyVelF2g==} - - ohash@1.1.2: - resolution: {integrity: sha512-9CIOSq5945rI045GFtcO3uudyOkYVY1nyfFxVQp+9BRgslr8jPNiSSrsFGg/BNTUFOLqx0P5tng6G32brIPw0w==} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - on-net-listen@1.1.2: resolution: {integrity: sha512-y1HRYy8s/RlcBvDUwKXSmkODMdx4KSuIvloCnQYJ2LdBBC1asY4HtfhXwe3UWknLakATZDnbzht2Ijw3M1EqFg==} engines: {node: '>=9.4.0 || ^8.9.4'} @@ -6805,10 +6569,6 @@ packages: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} - open@8.4.2: - resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} - engines: {node: '>=12'} - opn@5.5.0: resolution: {integrity: sha512-PqHpggC9bLV0VeWcdKhkpxY+3JTzetLSqTCWL/z/tFIbI6G8JCjondXklT1JinczLz2Xib62sSp0T/gKT4KksA==} engines: {node: '>=4'} @@ -6825,10 +6585,6 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} - ora@6.3.0: - resolution: {integrity: sha512-1/D8uRFY0ay2kgBpmAwmSA404w4OoPVhHMqRqtjvrcK/dnzcEZxMJ+V4DUbyICu8IIVRclHcOf5wlD1tMY4GUQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - os-browserify@0.3.0: resolution: {integrity: sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==} @@ -6843,6 +6599,9 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-cancelable@1.1.0: resolution: {integrity: sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==} engines: {node: '>=6'} @@ -6895,6 +6654,9 @@ packages: resolution: {integrity: sha512-Fd9lT9vJbHYRACT8OhCbZBbxr6KRSawSovFpy8nDGshaK99S/EBhVIHp9+crhxrsZOuvLpgL1n23iyPg6Rl2hg==} engines: {node: '>= 14'} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-json@6.5.0: resolution: {integrity: sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==} engines: {node: '>=8'} @@ -6903,6 +6665,9 @@ packages: resolution: {integrity: sha512-hySwcV8RAWeAfPsXb9/HGSPn8lwDnv6fabH+obUZKX169QknRkRhPxd1yMubpKDskLFATkl3jHpNtVtDPFA0Wg==} engines: {node: '>=14.16'} + package-manager-detector@0.2.2: + resolution: {integrity: sha512-VgXbyrSNsml4eHWIvxxG/nTL4wgybMTXCV2Un/+yEc3aDKKU6nQBZjbeP3Pl3qm9Qg92X/1ng4ffvCeD/zwHgg==} + pacote@13.6.2: resolution: {integrity: sha512-Gu8fU3GsvOPkak2CkbojR7vjs3k3P9cA6uazKTHdsdV0gpCEQq2opelnEv30KRQWgVzP5Vd/5umjcedma3MKtg==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -6931,10 +6696,6 @@ packages: parse-entities@4.0.1: resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} - parse-git-config@3.0.0: - resolution: {integrity: sha512-wXoQGL1D+2COYWCD35/xbiKma1Z15xvZL8cI25wvxzled58V51SJM04Urt/uznS900iQor7QO04SgdfT/XlbuA==} - engines: {node: '>=8'} - parse-github-url@1.0.2: resolution: {integrity: sha512-kgBf6avCbO3Cn6+RnzRGLkUsv4ZVqv/VfAYkRsyBcgkshNvVBkRn1FEZcW0Jb+npXQWm2vHPnnOqFteZxRRGNw==} engines: {node: '>=0.10.0'} @@ -6948,19 +6709,9 @@ packages: resolution: {integrity: sha512-SA5aMiaIjXkAiBrW/yPgLgQAQg42f7K3ACO+2l/zOvtQBwX58DMUsFJXelW2fx3yMBmWOVkR6j1MGsdSbCA4UA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - parse-path@7.0.0: - resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} - - parse-url@8.1.0: - resolution: {integrity: sha512-xDvOoLU5XRrcOZvnI6b8zA6n9O9ejNk/GExuz1yBuWUGn9KA97GI6HTs6u02wKara1CeVmZhH+0TZFdWScR89w==} - parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -6991,19 +6742,23 @@ packages: resolution: {integrity: sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==} engines: {node: '>= 0.8.0'} - path-scurry@1.10.1: - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} - engines: {node: '>=16 || 14 >=14.17'} + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - pathe@1.1.0: - resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} @@ -7019,19 +6774,23 @@ packages: pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} - perfect-debounce@0.1.3: - resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==} - performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pidtree@0.6.0: resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} engines: {node: '>=0.10'} @@ -7053,233 +6812,66 @@ packages: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} - pkg-types@1.0.2: - resolution: {integrity: sha512-hM58GKXOcj8WTqUXnsQyJYXdeAPbythQgEF3nTcEo+nkD49chjQ9IKm/QJy9xf6JakXptz86h7ecP2024rrLaQ==} - pkg-up@3.1.0: resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} engines: {node: '>=8'} + playwright-core@1.48.2: + resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.48.2: + resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==} + engines: {node: '>=18'} + hasBin: true + pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - - postcss-colormin@6.0.0: - resolution: {integrity: sha512-EuO+bAUmutWoZYgHn2T1dG1pPqHU6L4TjzPlu4t1wZGXQ/fxV16xg2EJmYi0z+6r+MGV1yvpx1BHkUaRrPa2bw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-convert-values@6.0.0: - resolution: {integrity: sha512-U5D8QhVwqT++ecmy8rnTb+RL9n/B806UVaS3m60lqle4YDFcpbS3ae5bTQIh3wOGUSDHSEtMYLs/38dNG7EYFw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-comments@6.0.0: - resolution: {integrity: sha512-p2skSGqzPMZkEQvJsgnkBhCn8gI7NzRH2683EEjrIkoMiwRELx68yoUJ3q3DGSGuQ8Ug9Gsn+OuDr46yfO+eFw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-duplicates@6.0.0: - resolution: {integrity: sha512-bU1SXIizMLtDW4oSsi5C/xHKbhLlhek/0/yCnoMQany9k3nPBq+Ctsv/9oMmyqbR96HYHxZcHyK2HR5P/mqoGA==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-empty@6.0.0: - resolution: {integrity: sha512-b+h1S1VT6dNhpcg+LpyiUrdnEZfICF0my7HAKgJixJLW7BnNmpRH34+uw/etf5AhOlIhIAuXApSzzDzMI9K/gQ==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-overridden@6.0.0: - resolution: {integrity: sha512-4VELwssYXDFigPYAZ8vL4yX4mUepF/oCBeeIT4OXsJPYOtvJumyz9WflmJWTfDwCUcpDR+z0zvCWBXgTx35SVw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-import-resolver@2.0.0: - resolution: {integrity: sha512-y001XYgGvVwgxyxw9J1a5kqM/vtmIQGzx34g0A0Oy44MFcy/ZboZw1hu/iN3VYFjSTRzbvd7zZJJz0Kh0AGkTw==} - postcss-import@13.0.0: resolution: {integrity: sha512-LPUbm3ytpYopwQQjqgUH4S3EM/Gb9QsaSPP/5vnoi+oKVy3/mIk2sc0Paqw7RL57GpScm9MdIMUypw2znWiBpg==} engines: {node: '>=10.0.0'} peerDependencies: postcss: ^8.0.0 - postcss-import@15.1.0: - resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} - engines: {node: '>=14.0.0'} - peerDependencies: - postcss: ^8.0.0 - - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} peerDependencies: + jiti: '>=1.21.0' postcss: '>=8.0.9' - ts-node: '>=9.0.0' + tsx: ^4.8.1 + yaml: ^2.4.2 peerDependenciesMeta: + jiti: + optional: true postcss: optional: true - ts-node: + tsx: + optional: true + yaml: optional: true - postcss-merge-longhand@6.0.0: - resolution: {integrity: sha512-4VSfd1lvGkLTLYcxFuISDtWUfFS4zXe0FpF149AyziftPFQIWxjvFSKhA4MIxMe4XM3yTDgQMbSNgzIVxChbIg==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-rules@6.0.0: - resolution: {integrity: sha512-rCXkklftzEkniyv3f4mRCQzxD6oE4Quyh61uyWTUbCJ26Pv2hoz+fivJSsSBWxDBeScR4fKCfF3HHTcD7Ybqnw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss-minify-font-values@6.0.0: - resolution: {integrity: sha512-zNRAVtyh5E8ndZEYXA4WS8ZYsAp798HiIQ1V2UF/C/munLp2r1UGHwf1+6JFu7hdEhJFN+W1WJQKBrtjhFgEnA==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 + postcss@8.4.14: + resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} + engines: {node: ^10 || ^12 || >=14} - postcss-minify-gradients@6.0.0: - resolution: {integrity: sha512-wO0F6YfVAR+K1xVxF53ueZJza3L+R3E6cp0VwuXJQejnNUH0DjcAFe3JEBeTY1dLwGa0NlDWueCA1VlEfiKgAA==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 + postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} - postcss-minify-params@6.0.0: - resolution: {integrity: sha512-Fz/wMQDveiS0n5JPcvsMeyNXOIMrwF88n7196puSuQSWSa+/Ofc1gDOSY2xi8+A4PqB5dlYCKk/WfqKqsI+ReQ==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 + postcss@8.4.23: + resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} + engines: {node: ^10 || ^12 || >=14} - postcss-minify-selectors@6.0.0: - resolution: {integrity: sha512-ec/q9JNCOC2CRDNnypipGfOhbYPuUkewGwLnbv6omue/PSASbHSU7s6uSQ0tcFRVv731oMIx8k0SP4ZX6be/0g==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-charset@6.0.0: - resolution: {integrity: sha512-cqundwChbu8yO/gSWkuFDmKrCZ2vJzDAocheT2JTd0sFNA4HMGoKMfbk2B+J0OmO0t5GUkiAkSM5yF2rSLUjgQ==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-display-values@6.0.0: - resolution: {integrity: sha512-Qyt5kMrvy7dJRO3OjF7zkotGfuYALETZE+4lk66sziWSPzlBEt7FrUshV6VLECkI4EN8Z863O6Nci4NXQGNzYw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-positions@6.0.0: - resolution: {integrity: sha512-mPCzhSV8+30FZyWhxi6UoVRYd3ZBJgTRly4hOkaSifo0H+pjDYcii/aVT4YE6QpOil15a5uiv6ftnY3rm0igPg==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-repeat-style@6.0.0: - resolution: {integrity: sha512-50W5JWEBiOOAez2AKBh4kRFm2uhrT3O1Uwdxz7k24aKtbD83vqmcVG7zoIwo6xI2FZ/HDlbrCopXhLeTpQib1A==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-string@6.0.0: - resolution: {integrity: sha512-KWkIB7TrPOiqb8ZZz6homet2KWKJwIlysF5ICPZrXAylGe2hzX/HSf4NTX2rRPJMAtlRsj/yfkrWGavFuB+c0w==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-timing-functions@6.0.0: - resolution: {integrity: sha512-tpIXWciXBp5CiFs8sem90IWlw76FV4oi6QEWfQwyeREVwUy39VSeSqjAT7X0Qw650yAimYW5gkl2Gd871N5SQg==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-unicode@6.0.0: - resolution: {integrity: sha512-ui5crYkb5ubEUDugDc786L/Me+DXp2dLg3fVJbqyAl0VPkAeALyAijF2zOsnZyaS1HyfPuMH0DwyY18VMFVNkg==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-url@6.0.0: - resolution: {integrity: sha512-98mvh2QzIPbb02YDIrYvAg4OUzGH7s1ZgHlD3fIdTHLgPLRpv1ZTKJDnSAKr4Rt21ZQFzwhGMXxpXlfrUBKFHw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-whitespace@6.0.0: - resolution: {integrity: sha512-7cfE1AyLiK0+ZBG6FmLziJzqQCpTQY+8XjMhMAz8WSBSCsCNNUKujgIgjCAmDT3cJ+3zjTXFkoD15ZPsckArVw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-ordered-values@6.0.0: - resolution: {integrity: sha512-K36XzUDpvfG/nWkjs6d1hRBydeIxGpKS2+n+ywlKPzx1nMYDYpoGbcjhj5AwVYJK1qV2/SDoDEnHzlPD6s3nMg==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-initial@6.0.0: - resolution: {integrity: sha512-s2UOnidpVuXu6JiiI5U+fV2jamAw5YNA9Fdi/GRK0zLDLCfXmSGqQtzpUPtfN66RtCbb9fFHoyZdQaxOB3WxVA==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-transforms@6.0.0: - resolution: {integrity: sha512-FQ9f6xM1homnuy1wLe9lP1wujzxnwt1EwiigtWwuyf8FsqqXUDUp2Ulxf9A5yjlUOTdCJO6lonYjg1mgqIIi2w==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-selector-parser@6.0.11: - resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} - engines: {node: '>=4'} - - postcss-svgo@6.0.0: - resolution: {integrity: sha512-r9zvj/wGAoAIodn84dR/kFqwhINp5YsJkLoujybWG59grR/IHx+uQ2Zo+IcOwM0jskfYX3R0mo+1Kip1VSNcvw==} - engines: {node: ^14 || ^16 || >= 18} - peerDependencies: - postcss: ^8.2.15 - - postcss-unique-selectors@6.0.0: - resolution: {integrity: sha512-EPQzpZNxOxP7777t73RQpZE5e9TrnCrkvp7AH7a0l89JmZiPnS82y216JowHXwpBCQitfyxrof9TK3rYbi7/Yw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-url@10.1.3: - resolution: {integrity: sha512-FUzyxfI5l2tKmXdYc6VTu3TWZsInayEKPbiyW+P6vmmIrrb4I6CGX0BFoewgYHLK+oIL5FECEK02REYRpBvUCw==} - engines: {node: '>=10'} - peerDependencies: - postcss: ^8.0.0 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.4.14: - resolution: {integrity: sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.4.21: - resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} - engines: {node: ^10 || ^12 || >=14} - - postcss@8.4.23: - resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==} - engines: {node: ^10 || ^12 || >=14} - - preferred-pm@3.0.3: - resolution: {integrity: sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==} - engines: {node: '>=10'} + postcss@8.4.47: + resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} + engines: {node: ^10 || ^12 || >=14} prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} @@ -7313,11 +6905,6 @@ packages: peerDependencies: prettier: ^2.0.0 - prettier@1.19.1: - resolution: {integrity: sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==} - engines: {node: '>=4'} - hasBin: true - prettier@2.7.1: resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} engines: {node: '>=10.13.0'} @@ -7327,10 +6914,6 @@ packages: resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} engines: {node: '>=6'} - pretty-bytes@6.1.0: - resolution: {integrity: sha512-Rk753HI8f4uivXi4ZCIYdhmG1V+WKzvRMg/X+M42a6t7D07RcmopXJMDNk6N++7Bl75URRGsb40ruvg7Hcp2wQ==} - engines: {node: ^14.13.1 || >=16.0.0} - pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} @@ -7374,10 +6957,6 @@ packages: resolution: {integrity: sha512-Hdd7GgV7b76Yh9FP9HL1D9xqtJCJdVPpiM2vDtuoc8W1KfweJe15gutFYmxkq83ViFaagFM8K0UcPCQ/tZq8bA==} engines: {node: '>= 6'} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -7400,13 +6979,6 @@ packages: resolution: {integrity: sha512-hNp56d5uuREVde7UqP+dmBkwzxrhJwYU5nL/mdivyFfkRZdgAgojkyBeU3jKo7ZHrjdSx6Q1CwUmYJI6INt20g==} hasBin: true - protocols@2.0.1: - resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - proxy-agent@6.3.1: resolution: {integrity: sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ==} engines: {node: '>= 14'} @@ -7414,9 +6986,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - prr@1.0.1: - resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==} - ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -7473,7 +7042,7 @@ packages: puppeteer@21.7.0: resolution: {integrity: sha512-Yy+UUy0b9siJezbhHO/heYUoZQUwyqDK1yOQgblTt0l97tspvDVFkcW9toBlnSvSfkDmMI3Dx9cZL6R8bDArHA==} engines: {node: '>=16.13.2'} - deprecated: < 22.6.4 is no longer supported + deprecated: < 22.8.2 is no longer supported hasBin: true pvtsutils@1.3.2: @@ -7486,10 +7055,10 @@ packages: q@1.5.1: resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} engines: {node: '>=0.6.0', teleport: '>=0.2.0'} + deprecated: |- + You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qs@6.5.3: resolution: {integrity: sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==} @@ -7525,29 +7094,15 @@ packages: resolution: {integrity: sha512-kKr2uQ2AokadPjvTyKJQad9xELbZwYzWlNfI3Uz2j/ib5u6H9lDP7fUUR//rMycd0gv4Z5P1qXMfXR8YpIxrjQ==} hasBin: true - radix3@1.0.1: - resolution: {integrity: sha512-y+AcwZ3HcUIGc9zGsNVf5+BY/LxL+z+4h4J3/pp8jxSmy1STaCocPS3qrj4tA5ehUSzqtqK+0Aygvz/r/8vy4g==} - randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} randomfill@1.0.4: resolution: {integrity: sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==} - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.1: - resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} - engines: {node: '>= 0.8'} - rc-config-loader@4.1.2: resolution: {integrity: sha512-qKTnVWFl9OQYKATPzdfaZIbTxcHziQl92zYSxYC6umhOqyAsoj8H8Gq/+aFjAso68sBdjTz3A7omqeAkkF1MWg==} - rc9@2.1.0: - resolution: {integrity: sha512-ROO9bv8PPqngWKoiUZU3JDQ4sugpdRs9DfwHnzDSxK25XtQn6BEHL6EOd/OtKuDT2qodrtNR+0WkPT6l0jxH5Q==} - rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -7592,6 +7147,7 @@ packages: read-package-json@5.0.2: resolution: {integrity: sha512-BSzugrt4kQ/Z0krro8zhTwV1Kd79ue25IhNN/VtHFy1mG/6Tluyi+msc0UpwaoQzxSHa28mntAjIZY6kEgfR9Q==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. Please use @npmcli/package-json instead. read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -7616,9 +7172,6 @@ packages: resolution: {integrity: sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==} engines: {node: '>= 6'} - readdir-glob@1.1.3: - resolution: {integrity: sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -7627,14 +7180,6 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} - redis-errors@1.2.0: - resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} - engines: {node: '>=4'} - - redis-parser@3.0.0: - resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==} - engines: {node: '>=4'} - regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} @@ -7899,10 +7444,6 @@ packages: resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} engines: {node: '>=8'} - restore-cursor@4.0.0: - resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - retimer@3.0.0: resolution: {integrity: sha512-WKE0j11Pa0ZJI5YIk0nflGI7SQsfl2ljihVy7ogh7DeQSeYAUi0ubZ/yEueGtDfUPk6GH5LRw1hBdLq4IwUBWA==} @@ -7919,6 +7460,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true ripemd160@2.0.2: @@ -7927,26 +7469,16 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} - rollup-plugin-visualizer@5.9.0: - resolution: {integrity: sha512-bbDOv47+Bw4C/cgs0czZqfm8L82xOZssk4ayZjG40y9zbXclNk7YikrZTDao6p7+HDiGxrN0b65SgZiVm9k1Cg==} - engines: {node: '>=14'} - hasBin: true - peerDependencies: - rollup: 2.x || 3.x - peerDependenciesMeta: - rollup: - optional: true - - rollup@2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true - rollup@3.21.0: resolution: {integrity: sha512-ANPhVcyeHvYdQMUyCbczy33nbLzI7RzrBje4uvNiTDJGIMtlKoOStmympwr9OtS1LZxiDmE2wvxHyVhoLtf1KQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true + rollup@4.24.0: + resolution: {integrity: sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + run-async@2.4.1: resolution: {integrity: sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==} engines: {node: '>=0.12.0'} @@ -7990,16 +7522,9 @@ packages: scope-analyzer@2.1.2: resolution: {integrity: sha512-5cfCmsTYV/wPaRIItNxatw02ua/MThdIUNnUOCYp+3LSEJvnG804ANw2VLaavNILIfWXF1D1G2KNANkBBvInwQ==} - scule@1.0.0: - resolution: {integrity: sha512-4AsO/FrViE/iDNEPaAQlb77tf0csuq27EsVpy6ett584EcRTp6pTDLoGWVxCD77y5iU5FauOvhsI4o1APwPoSQ==} - seedrandom@3.0.5: resolution: {integrity: sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==} - selenium-webdriver@4.12.0: - resolution: {integrity: sha512-zvPzmTsky6WfO6+BGMj2mCJsw7qKnfQONur2b+pGn8jeTiC+WAUOthZOnaK+HkX5wiU6L4uoMF+JIcOVstp25A==} - engines: {node: '>= 14.20.0'} - semver-diff@3.1.1: resolution: {integrity: sha512-GX0Ix/CJcHyB8c4ykpHGIAvLyOwOobtM/8d+TQkAd81/bEjgPHrfba41Vpesr7jX/t8Uh+R3EX9eAS5be+jQYg==} engines: {node: '>=8'} @@ -8011,10 +7536,6 @@ packages: semver-utils@1.1.4: resolution: {integrity: sha512-EjnoLE5OGmDAVV/8YDoN5KiajNadjzIp9BAHOhYeQHt7j0UWxjmgsx4YD48wp4Ue1Qogq38F1GNUJNqF1kKKxA==} - semver@5.7.1: - resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} - hasBin: true - semver@5.7.2: resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} hasBin: true @@ -8061,29 +7582,15 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} - serialize-javascript@6.0.1: resolution: {integrity: sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==} - serve-placeholder@2.0.1: - resolution: {integrity: sha512-rUzLlXk4uPFnbEaIz3SW8VISTxMuONas88nYWjAWaM2W9VDbt9tyFOr3lq8RhVOFrT3XISoBw8vni5una8qMnQ==} - - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} - set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} setimmediate@1.0.5: resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.11: resolution: {integrity: sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==} hasBin: true @@ -8130,6 +7637,9 @@ packages: side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -8156,6 +7666,10 @@ packages: sinusoidal-decimal@1.0.0: resolution: {integrity: sha512-KPUi1ZqLocV64n0AuV+g4VDjAM+tEEY66nUd+rYaVBHIfeheGGUvIxe9bf7Mpc1PonDTVW2uRr9nigQa9odvuA==} + sirv@2.0.4: + resolution: {integrity: sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==} + engines: {node: '>= 10'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -8188,14 +7702,6 @@ packages: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - smartwrap@2.0.2: - resolution: {integrity: sha512-vCsKNQxb7PnCNd2wY1WClWifAc2lwqsG8OaswpJkVJsvMGcnEntdTCDajZCkk93Ay1U3t/9puJmb525Rg5MZBA==} - engines: {node: '>=6'} - hasBin: true - - smob@0.0.6: - resolution: {integrity: sha512-V21+XeNni+tTyiST1MHsa84AQhT1aFZipzPpOFAVB8DkHzwJyjjAmt9bgwnuZiZWnIbMo2duE29wybxv/7HWUw==} - socks-proxy-agent@7.0.0: resolution: {integrity: sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==} engines: {node: '>= 10'} @@ -8234,10 +7740,6 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} - source-map@0.7.4: - resolution: {integrity: sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==} - engines: {node: '>= 8'} - source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} @@ -8301,6 +7803,9 @@ packages: stack-generator@2.0.10: resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} @@ -8310,9 +7815,6 @@ packages: stacktrace-js@2.0.2: resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} - standard-as-callback@2.1.0: - resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==} - static-eval@2.1.0: resolution: {integrity: sha512-agtxZ/kWSsCkI5E4QifRwsaPs0P0JmZV6dkLz6ILYfFYQGn+5plctanRN+IC8dJRiFkyXHrwEE3W9Wmx67uDbw==} @@ -8323,12 +7825,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.3.2: - resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} - - stdin-discarder@0.1.0: - resolution: {integrity: sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + std-env@3.7.0: + resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} stop-iteration-iterator@1.0.0: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} @@ -8359,9 +7857,6 @@ packages: resolution: {integrity: sha512-whIqf/ljJ88dr0z6iNFtJq09rs4R6JxJOnIqGthC3rHFEMYq6ssm4sPYILXEPrFYncMjF39An6MBys1o5BC19w==} engines: {node: '>=4.0.0'} - stream-transform@2.1.3: - resolution: {integrity: sha512-9GHUiM5hMiCi6Y03jD2ARC1ettBXkQBoQAe7nJsPknnI0ow10aXjTnew8QtYQmLjzn974BnmWEAJgCY6ZP1DeQ==} - streaming-json-stringify@3.1.0: resolution: {integrity: sha512-axtfs3BDxAsrZ9swD163FBrXZ8dhJJp6kUI6C97TvUZG9RHKfbg9nFbXqEheFNOb3IYMEt2ag9F62sWLFUZ4ug==} @@ -8372,6 +7867,9 @@ packages: streamx@2.15.1: resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-argv@0.3.1: resolution: {integrity: sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==} engines: {node: '>=0.6.19'} @@ -8441,10 +7939,6 @@ packages: resolution: {integrity: sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==} engines: {node: '>=12'} - strip-ansi@7.1.0: - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} - engines: {node: '>=12'} - strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -8477,9 +7971,6 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@1.0.1: - resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} - strtok3@9.0.1: resolution: {integrity: sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==} engines: {node: '>=16'} @@ -8497,21 +7988,15 @@ packages: babel-plugin-macros: optional: true - stylehacks@6.0.0: - resolution: {integrity: sha512-+UT589qhHPwz6mTlCLSt/vMNTJx8dopeJlZAlBMJPWA3ORqu6wmQY7FBXf+qD+FsqoBJODyqNxOUP3jdntFRdw==} - engines: {node: ^14 || ^16 || >=18.0} - peerDependencies: - postcss: ^8.2.15 - stylis@4.2.0: resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} subarg@1.0.0: resolution: {integrity: sha512-RIrIdRY0X1xojthNcVtgT9sjpOGagEUKpZdgBUi054OEPFo282yg+zE+t1Rj3+RqKq2xStL7uUHhY+AjbC4BXg==} - sucrase@3.34.0: - resolution: {integrity: sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==} - engines: {node: '>=8'} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} hasBin: true summary@2.1.0: @@ -8541,19 +8026,11 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - svg-tags@1.0.0: - resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - svgo@2.8.0: resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} engines: {node: '>=10.13.0'} hasBin: true - svgo@3.0.2: - resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} - engines: {node: '>=14.0.0'} - hasBin: true - syncpack@8.2.4: resolution: {integrity: sha512-Tq1RzTqQwGCe8htWqJPHcA55LqGLO4wG1F2DCGMFflfX3ujbRoiXq2MYd7XFvm3mzxv1BPVQQp8DU2ZkY14e2A==} engines: {node: '>=10'} @@ -8569,10 +8046,6 @@ packages: tachyons@4.12.0: resolution: {integrity: sha512-2nA2IrYFy3raCM9fxJ2KODRGHVSZNTW3BR0YnlGsLUf1DA3pk3YfWZ/DdfbnZK6zLZS+jUenlUGJsKcA5fUiZg==} - tapable@1.1.3: - resolution: {integrity: sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==} - engines: {node: '>=6'} - tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -8624,6 +8097,10 @@ packages: engines: {node: '>=10'} hasBin: true + test-exclude@7.0.1: + resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} + engines: {node: '>=18'} + text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -8658,17 +8135,32 @@ packages: resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} engines: {node: '>=8'} - tiny-invariant@1.3.1: - resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==} + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.1: + resolution: {integrity: sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==} + + tinyglobby@0.2.9: + resolution: {integrity: sha512-8or1+BGEdk1Zkkw2ii16qSS7uVrQJPre5A9o/XkWPATkk23FZh/15BKFxPnlTy6vkljZxLqYCzzBMj30ZrSvjw==} + engines: {node: '>=12.0.0'} + + tinypool@1.0.1: + resolution: {integrity: sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} tmp@0.0.33: resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} engines: {node: '>=0.6.0'} - tmp@0.2.1: - resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==} - engines: {node: '>=8.17.0'} - to-fast-properties@2.0.0: resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} engines: {node: '>=4'} @@ -8693,14 +8185,14 @@ packages: toggle-selection@1.0.6: resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - token-types@6.0.0: resolution: {integrity: sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==} engines: {node: '>=14.16'} + totalist@3.0.1: + resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} + engines: {node: '>=6'} + touch@3.1.0: resolution: {integrity: sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==} hasBin: true @@ -8713,6 +8205,10 @@ packages: resolution: {integrity: sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -8775,15 +8271,18 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsup@6.1.3: - resolution: {integrity: sha512-eRpBnbfpDFng+EJNTQ90N7QAf4HAGGC7O3buHIjroKWK7D1ibk9/YnR/3cS8HsMU5T+6Oi+cnF+yU5WmCnB//Q==} - engines: {node: '>=14'} + tsup@8.3.0: + resolution: {integrity: sha512-ALscEeyS03IomcuNdFdc0YWGVIkwH1Ws7nfTbAPuoILvEV2hpGQAY72LIOjglGo4ShWpZfpBqP/jpQVCzqYQag==} + engines: {node: '>=18'} hasBin: true peerDependencies: + '@microsoft/api-extractor': ^7.36.0 '@swc/core': ^1 postcss: ^8.4.12 - typescript: ^4.1.0 + typescript: '>=4.5.0' peerDependenciesMeta: + '@microsoft/api-extractor': + optional: true '@swc/core': optional: true postcss: @@ -8803,11 +8302,6 @@ packages: tty-browserify@0.0.1: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} - tty-table@4.1.6: - resolution: {integrity: sha512-kRj5CBzOrakV4VRRY5kUWbNYvo/FpOsz65DzI5op9P+cHov3+IqPbo1JE1ZnQGkHdZgNFDsrEjrfqqy/Ply9fw==} - engines: {node: '>=8.0.0'} - hasBin: true - tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} @@ -8902,10 +8396,6 @@ packages: type-component@0.0.1: resolution: {integrity: sha512-mDZRBQS2yZkwRQKfjJvQ8UIYJeBNNWCq+HBNstl9N5s9jZ4dkVYXEGkVPsSCEh5Ld4JM1kmrZTzjnrqSAIQ7dw==} - type-fest@0.13.1: - resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} - engines: {node: '>=10'} - type-fest@0.18.1: resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} engines: {node: '>=10'} @@ -8934,13 +8424,9 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@3.9.0: - resolution: {integrity: sha512-hR8JP2e8UiH7SME5JZjsobBlEiatFoxpzCP+R3ZeCo7kAaG1jXQE5X/buLzogM6GJu8le9Y4OcfNuIQX0rZskA==} - engines: {node: '>=14.16'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} + type-fest@4.26.1: + resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} + engines: {node: '>=16'} type@1.2.0: resolution: {integrity: sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==} @@ -8975,9 +8461,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - ufo@1.1.1: - resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==} - uint8array-extras@1.4.0: resolution: {integrity: sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==} engines: {node: '>=18'} @@ -8992,12 +8475,6 @@ packages: unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} - uncrypto@0.1.2: - resolution: {integrity: sha512-kuZwRKV615lEw/Xx3Iz56FKk3nOeOVGaVmw0eg+x4Mne28lCotNFbBhDW7dEBCBKyKbRQiCadEZeNAFPVC5cgw==} - - unctx@2.3.0: - resolution: {integrity: sha512-xs79V1T5JEQ/5aQ3j4ipbQEaReMosMz/ktOdsZMEtKv1PfbdRrKY/PaU0CxdspkX3zEink2keQU4nRzAXgui1A==} - undeclared-identifiers@1.1.3: resolution: {integrity: sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==} hasBin: true @@ -9005,11 +8482,8 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - unenv@1.4.1: - resolution: {integrity: sha512-DuFZUDfaBC92zy3fW7QqKTLdYJIPkpwTN0yGZtaxnpOI7HvIfl41NYh9NVv4zcqhT8CGXJ1ELpvO2tecaB6NfA==} - - unhead@1.1.26: - resolution: {integrity: sha512-MshcPoPLXSGRgYtczddGvMgLUISTbt2pxihqD5kZVXKmY2FZLj1OQIY111aX45Xq47XJxjvYavvoyeUFroKQcg==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} unified-args@10.0.0: resolution: {integrity: sha512-PqsqxwkXpGSLiMkbjNnKU33Ffm6gso6rAvz1TlBGzMBx3gpx7ewIhViBX8HEWmy0v7pebA5PM6RkRWWaYmtfYw==} @@ -9038,9 +8512,6 @@ packages: unified@9.2.2: resolution: {integrity: sha512-Sg7j110mtefBD+qunSLO1lqOEKdrwBFBrR6Qd8f4uwkhWNlbkaqwHse6e7QvD3AP/MNoJdEDLaf8OxYyoWgorQ==} - unimport@3.0.6: - resolution: {integrity: sha512-GYxGJ1Bri1oqx8VFDjdgooGzeK7jBk3bvhXmamTIpu3nONOcUMGwZbX7X0L5RA7OWMXpR4vzpSQP7pXUzJg1/Q==} - uniq@1.0.1: resolution: {integrity: sha512-Gw+zz50YNKPDKXs+9d+aKAjVwpjNwqzvNpLigIruT4HA9lMZNdMqs9x07kKHB/L9WRzqp4+DlTU5s4wG2esdoA==} @@ -9136,43 +8607,6 @@ packages: resolution: {integrity: sha512-6bc58dPYhCMHHuwxldQxO3RRNZ4eCogZ/st++0+fcC1nr0jiGUtAdBJ2qzmLQWSxbtz42pWt4QQMiZ9HvZf5cg==} engines: {node: '>=0.10.0'} - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - unplugin@1.3.1: - resolution: {integrity: sha512-h4uUTIvFBQRxUKS2Wjys6ivoeofGhxzTe2sRWlooyjHXVttcVfV/JiavNd3d4+jty0SVV0dxGw9AkY9MwiaCEw==} - - unstorage@1.5.0: - resolution: {integrity: sha512-bL6sHwTKp2ns0SAGNHAbLP9LwmtPGMtaOVrHRA4V8ngQMHQR18q0uRgkeGB4qF84XSDu/o8ebv54p/HBJESXFA==} - peerDependencies: - '@azure/app-configuration': ^1.3.1 - '@azure/cosmos': ^3.17.3 - '@azure/data-tables': ^13.2.2 - '@azure/identity': ^3.1.4 - '@azure/keyvault-secrets': ^4.7.0 - '@azure/storage-blob': ^12.14.0 - '@planetscale/database': ^1.7.0 - peerDependenciesMeta: - '@azure/app-configuration': - optional: true - '@azure/cosmos': - optional: true - '@azure/data-tables': - optional: true - '@azure/identity': - optional: true - '@azure/keyvault-secrets': - optional: true - '@azure/storage-blob': - optional: true - '@planetscale/database': - optional: true - - untyped@1.3.2: - resolution: {integrity: sha512-z219Z65rOGD6jXIvIhpZFfwWdqQckB8sdZec2NO+TkcH1Bph7gL0hwLzRJs1KsOo4Jz4mF9guBXhsEnyEBGVfw==} - hasBin: true - update-browserslist-db@1.0.11: resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} hasBin: true @@ -9221,10 +8655,6 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - uuid-parse@1.1.0: resolution: {integrity: sha512-OdmXxA8rDsQ7YpNVbKSJkNzTw2I+S5WsbMDnCtIWSQaosNAcWtFuI/YK1TjzUI6nbkgiqEyh8gWngfcv8Asd9A==} @@ -9272,10 +8702,6 @@ packages: varint@5.0.2: resolution: {integrity: sha512-lKxKYG6H03yCZUpAGOPOsMcGxd1RHCu1iKvEHYDPmTyq2HueGhD73ssNBqqQWfvYs04G9iUFRvmAVLW20Jw6ow==} - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - verror@1.10.0: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} @@ -9319,50 +8745,49 @@ packages: vfile@6.0.1: resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} - vite-node@0.30.1: - resolution: {integrity: sha512-vTikpU/J7e6LU/8iM3dzBo8ZhEiKZEKRznEMm+mJh95XhWaPrJQraT/QsT2NWmuEf+zgAoMe64PKT7hfZ1Njmg==} - engines: {node: '>=v14.18.0'} + vite-node@2.1.3: + resolution: {integrity: sha512-I1JadzO+xYX887S39Do+paRePCKoiDrWRRjp9kkG5he0t7RXNvPAJPCQSJqbGN4uCrFFeS3Kj3sLqY8NMYBEdA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-plugin-checker@0.5.6: - resolution: {integrity: sha512-ftRyON0gORUHDxcDt2BErmsikKSkfvl1i2DoP6Jt2zDO9InfvM6tqO1RkXhSjkaXEhKPea6YOnhFaZxW3BzudQ==} - engines: {node: '>=14.16'} + vite-plugin-arraybuffer@0.0.8: + resolution: {integrity: sha512-F+InDQuxd93YDVRdXTjBr3lydgjyVHSpgRZS4izK/i85Anl5kmbvf2NwJ//XHAccHh1TScfX70MFJfBx/rf3cg==} + + vite@4.3.2: + resolution: {integrity: sha512-9R53Mf+TBoXCYejcL+qFbZde+eZveQLDYd9XgULILLC1a5ZwPaqgmdVpL8/uvw2BM/1TzetWjglwm+3RO+xTyw==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true peerDependencies: - eslint: '>=7' - meow: ^9.0.0 - optionator: ^0.9.1 - stylelint: '>=13' - typescript: '*' - vite: '>=2.0.0' - vls: '*' - vti: '*' - vue-tsc: '*' + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 peerDependenciesMeta: - eslint: - optional: true - meow: - optional: true - optionator: + '@types/node': optional: true - stylelint: + less: optional: true - typescript: + sass: optional: true - vls: + stylus: optional: true - vti: + sugarss: optional: true - vue-tsc: + terser: optional: true - vite@4.3.2: - resolution: {integrity: sha512-9R53Mf+TBoXCYejcL+qFbZde+eZveQLDYd9XgULILLC1a5ZwPaqgmdVpL8/uvw2BM/1TzetWjglwm+3RO+xTyw==} - engines: {node: ^14.18.0 || >=16.0.0} + vite@5.4.10: + resolution: {integrity: sha512-1hvaPshuPUtxeQ0hsVH3Mud0ZanOLwVTneA1EgbAM5LhaZEqyPWGRQ7BtaMvUrTDeEaC8pxtj6a6jku3x4z6SQ==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: - '@types/node': '>= 14' + '@types/node': ^18.0.0 || >=20.0.0 less: '*' + lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -9371,8 +8796,12 @@ packages: optional: true less: optional: true + lightningcss: + optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -9380,47 +8809,37 @@ packages: terser: optional: true - vm-browserify@1.1.2: - resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} - - vscode-jsonrpc@6.0.0: - resolution: {integrity: sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==} - engines: {node: '>=8.0.0 || >=10.0.0'} - - vscode-languageclient@7.0.0: - resolution: {integrity: sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==} - engines: {vscode: ^1.52.0} - - vscode-languageserver-protocol@3.16.0: - resolution: {integrity: sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==} - - vscode-languageserver-textdocument@1.0.8: - resolution: {integrity: sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==} - - vscode-languageserver-types@3.16.0: - resolution: {integrity: sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==} - - vscode-languageserver@7.0.0: - resolution: {integrity: sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==} + vitest@2.1.3: + resolution: {integrity: sha512-Zrxbg/WiIvUP2uEzelDNTXmEMJXuzJ1kCpbDvaKByFA9MNeO95V+7r/3ti0qzJzrxdyuUw5VduN7k+D3VmVOSA==} + engines: {node: ^18.0.0 || >=20.0.0} hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.3 + '@vitest/ui': 2.1.3 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true - vscode-uri@3.0.7: - resolution: {integrity: sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==} + vm-browserify@1.1.2: + resolution: {integrity: sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==} vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vue-bundle-renderer@1.0.3: - resolution: {integrity: sha512-EfjX+5TTUl70bki9hPuVp+54JiZOvFIfoWBcfXsSwLzKEiDYyHNi5iX8srnqLIv3YRnvxgbntdcG1WPq0MvffQ==} - - vue-devtools-stub@0.1.0: - resolution: {integrity: sha512-RutnB7X8c5hjq39NceArgXg28WZtZpGc3+J16ljMiYnFhKvd8hITxSWQSQ5bvldxMDU6gG5mkxl1MTQLXckVSQ==} - - vue-router@4.1.6: - resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==} - peerDependencies: - vue: ^3.2.0 - vue-tsc@2.1.10: resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==} hasBin: true @@ -9429,9 +8848,15 @@ packages: vue@2.7.14: resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==} + deprecated: Vue 2 has reached EOL and is no longer actively maintained. See https://v2.vuejs.org/eol/ for more details. - vue@3.2.47: - resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==} + vue@3.5.12: + resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true walk-up-path@1.0.0: resolution: {integrity: sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg==} @@ -9469,9 +8894,6 @@ packages: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - webpack-virtual-modules@0.5.0: - resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==} - webpack@5.88.2: resolution: {integrity: sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==} engines: {node: '>=10.13.0'} @@ -9497,13 +8919,6 @@ packages: which-module@2.0.0: resolution: {integrity: sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==} - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - - which-pm@2.0.0: - resolution: {integrity: sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==} - engines: {node: '>=8.15'} - which-typed-array@1.1.9: resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==} engines: {node: '>= 0.4'} @@ -9517,6 +8932,11 @@ packages: engines: {node: '>= 8'} hasBin: true + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -9582,6 +9002,18 @@ packages: utf-8-validate: optional: true + ws@8.18.0: + resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.5.0: resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} engines: {node: '>=10.0.0'} @@ -9606,9 +9038,6 @@ packages: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - xxhashjs@0.2.2: - resolution: {integrity: sha512-AkTuIuVTET12tpsVIQo+ZU6f/qDmKuRUcjaqR+OIvm+aCBsZ95i7UVY5WJ9TMsSaZ0DA2WxoZ4acu0sPH+OKAw==} - y18n@4.0.3: resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} @@ -9633,12 +9062,13 @@ packages: resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} engines: {node: '>= 14'} - yargs-parser@15.0.3: - resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==} + yaml@2.6.0: + resolution: {integrity: sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==} + engines: {node: '>= 14'} + hasBin: true - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} + yargs-parser@15.0.3: + resolution: {integrity: sha512-/MVEVjTXy/cGAjdtQf8dW3V9b97bPN7rNn8ETj6BmAQL7ibC7O1Q9SPJbGjgh3SlwoBNXMzj/ZGIj8mBgl12YA==} yargs-parser@20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} @@ -9655,10 +9085,6 @@ packages: yargs@14.2.3: resolution: {integrity: sha512-ZbotRWhF+lkjijC/VhmOT9wSgyBQ7+zr13+YLkhfsSiTriYsMzkTUFP18pFhWwBeMa5gUc1MzbhrO6/VB7c9Xg==} - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - yargs@17.5.1: resolution: {integrity: sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==} engines: {node: '>=12'} @@ -9678,12 +9104,9 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zhead@2.0.4: - resolution: {integrity: sha512-V4R94t3ifk9AURym6OskbKcnowzgp5Z88tkoL/NF67vyryNxC62u6mx5F1Ux4oh4+YN7FFmKYEyWy6m5kfPH6g==} - - zip-stream@4.1.0: - resolution: {integrity: sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==} - engines: {node: '>= 10'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} zod@3.13.4: resolution: {integrity: sha512-LZRucWt4j/ru5azOkJxCfpR87IyFDn8h2UODdqvXzZLb3K7bb9chUrUIGTy3BPsr8XnbQYfQ5Md5Hu2OYIo1mg==} @@ -9705,7 +9128,7 @@ snapshots: concat-stream: 2.0.0 d3-fg: 6.14.0 debounce: 1.2.1 - debug: 4.3.4 + debug: 4.3.7 end-of-stream: 1.4.4 env-string: 1.0.1 escape-string-regexp: 4.0.0 @@ -9736,6 +9159,11 @@ snapshots: '@jridgewell/gen-mapping': 0.1.1 '@jridgewell/trace-mapping': 0.3.18 + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + '@ardatan/sync-fetch@0.0.1(encoding@0.1.13)': dependencies: node-fetch: 2.6.9(encoding@0.1.13) @@ -9785,10 +9213,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.18 jsesc: 2.5.2 - '@babel/helper-annotate-as-pure@7.18.6': - dependencies: - '@babel/types': 7.21.4 - '@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.4)': dependencies: '@babel/compat-data': 7.21.4 @@ -9798,38 +9222,20 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.21.4(@babel/core@7.21.4)': - dependencies: - '@babel/core': 7.21.4 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-function-name': 7.21.0 - '@babel/helper-member-expression-to-functions': 7.21.0 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/helper-replace-supers': 7.20.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.20.0 - '@babel/helper-split-export-declaration': 7.18.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-environment-visitor@7.18.9': {} '@babel/helper-function-name@7.21.0': dependencies: '@babel/template': 7.20.7 - '@babel/types': 7.21.4 + '@babel/types': 7.25.9 '@babel/helper-hoist-variables@7.18.6': dependencies: - '@babel/types': 7.21.4 - - '@babel/helper-member-expression-to-functions@7.21.0': - dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.25.9 '@babel/helper-module-imports@7.21.4': dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.25.9 '@babel/helper-module-transforms@7.21.2': dependencies: @@ -9844,34 +9250,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.18.6': - dependencies: - '@babel/types': 7.21.4 - '@babel/helper-plugin-utils@7.20.2': {} - '@babel/helper-replace-supers@7.20.7': - dependencies: - '@babel/helper-environment-visitor': 7.18.9 - '@babel/helper-member-expression-to-functions': 7.21.0 - '@babel/helper-optimise-call-expression': 7.18.6 - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.4 - '@babel/types': 7.21.4 - transitivePeerDependencies: - - supports-color - '@babel/helper-simple-access@7.20.2': dependencies: - '@babel/types': 7.21.4 - - '@babel/helper-skip-transparent-expression-wrappers@7.20.0': - dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.25.9 '@babel/helper-split-export-declaration@7.18.6': dependencies: - '@babel/types': 7.21.4 + '@babel/types': 7.25.9 '@babel/helper-string-parser@7.19.4': {} @@ -9901,19 +9288,9 @@ snapshots: dependencies: '@babel/types': 7.21.4 - '@babel/parser@7.26.2': + '@babel/parser@7.25.9': dependencies: - '@babel/types': 7.26.0 - - '@babel/plugin-syntax-jsx@7.21.4(@babel/core@7.21.4)': - dependencies: - '@babel/core': 7.21.4 - '@babel/helper-plugin-utils': 7.20.2 - - '@babel/plugin-syntax-typescript@7.21.4(@babel/core@7.21.4)': - dependencies: - '@babel/core': 7.21.4 - '@babel/helper-plugin-utils': 7.20.2 + '@babel/types': 7.25.9 '@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.4)': dependencies: @@ -9925,31 +9302,15 @@ snapshots: '@babel/core': 7.21.4 '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.4)': - dependencies: - '@babel/core': 7.21.4 - '@babel/helper-annotate-as-pure': 7.18.6 - '@babel/helper-create-class-features-plugin': 7.21.4(@babel/core@7.21.4) - '@babel/helper-plugin-utils': 7.20.2 - '@babel/plugin-syntax-typescript': 7.21.4(@babel/core@7.21.4) - transitivePeerDependencies: - - supports-color - '@babel/runtime-corejs3@7.21.0': dependencies: core-js-pure: 3.29.0 regenerator-runtime: 0.13.11 - '@babel/runtime@7.20.13': - dependencies: - regenerator-runtime: 0.13.11 - '@babel/runtime@7.21.0': dependencies: regenerator-runtime: 0.13.11 - '@babel/standalone@7.21.4': {} - '@babel/template@7.20.7': dependencies: '@babel/code-frame': 7.21.4 @@ -9966,7 +9327,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.18.6 '@babel/parser': 7.21.4 '@babel/types': 7.21.4 - debug: 4.3.4 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -9977,20 +9338,35 @@ snapshots: '@babel/helper-validator-identifier': 7.19.1 to-fast-properties: 2.0.0 - '@babel/types@7.26.0': + '@babel/types@7.25.9': dependencies: '@babel/helper-string-parser': 7.25.9 '@babel/helper-validator-identifier': 7.25.9 + '@bcoe/v8-coverage@0.2.3': {} + '@braintree/sanitize-url@3.1.0': {} - '@changesets/apply-release-plan@6.1.3': + '@bundled-es-modules/cookie@2.0.0': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/config': 2.3.0 - '@changesets/get-version-range-type': 0.3.2 - '@changesets/git': 2.0.0 - '@changesets/types': 5.2.1 + cookie: 0.5.0 + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + + '@changesets/apply-release-plan@7.0.5': + dependencies: + '@changesets/config': 3.0.3 + '@changesets/get-version-range-type': 0.4.0 + '@changesets/git': 3.0.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 detect-indent: 6.1.0 fs-extra: 7.0.1 @@ -9998,149 +9374,133 @@ snapshots: outdent: 0.5.0 prettier: 2.7.1 resolve-from: 5.0.0 - semver: 5.7.1 + semver: 7.5.4 - '@changesets/assemble-release-plan@5.2.3': + '@changesets/assemble-release-plan@6.0.4': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 - '@changesets/types': 5.2.1 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - semver: 5.7.1 - - '@changesets/changelog-git@0.1.14': - dependencies: - '@changesets/types': 5.2.1 - - '@changesets/cli@2.23.2': - dependencies: - '@babel/runtime': 7.20.13 - '@changesets/apply-release-plan': 6.1.3 - '@changesets/assemble-release-plan': 5.2.3 - '@changesets/changelog-git': 0.1.14 - '@changesets/config': 2.3.0 - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 - '@changesets/get-release-plan': 3.0.16 - '@changesets/git': 1.5.0 - '@changesets/logger': 0.0.5 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 - '@changesets/write': 0.1.9 + semver: 7.5.4 + + '@changesets/changelog-git@0.2.0': + dependencies: + '@changesets/types': 6.0.0 + + '@changesets/cli@2.27.9': + dependencies: + '@changesets/apply-release-plan': 7.0.5 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/changelog-git': 0.2.0 + '@changesets/config': 3.0.3 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/get-release-plan': 4.0.4 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/should-skip-package': 0.1.1 + '@changesets/types': 6.0.0 + '@changesets/write': 0.3.2 '@manypkg/get-packages': 1.1.3 - '@types/is-ci': 3.0.0 - '@types/semver': 6.2.3 ansi-colors: 4.1.3 - chalk: 2.4.2 + ci-info: 3.8.0 enquirer: 2.3.6 external-editor: 3.1.0 fs-extra: 7.0.1 - human-id: 1.0.2 - is-ci: 3.0.1 - meow: 6.1.1 - outdent: 0.5.0 + mri: 1.2.0 p-limit: 2.3.0 - preferred-pm: 3.0.3 + package-manager-detector: 0.2.2 + picocolors: 1.1.1 resolve-from: 5.0.0 - semver: 5.7.1 + semver: 7.5.4 spawndamnit: 2.0.0 term-size: 2.2.1 - tty-table: 4.1.6 - '@changesets/config@2.3.0': + '@changesets/config@3.0.3': dependencies: - '@changesets/errors': 0.1.4 - '@changesets/get-dependents-graph': 1.3.5 - '@changesets/logger': 0.0.5 - '@changesets/types': 5.2.1 + '@changesets/errors': 0.2.0 + '@changesets/get-dependents-graph': 2.1.2 + '@changesets/logger': 0.1.1 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 micromatch: 4.0.5 - '@changesets/errors@0.1.4': + '@changesets/errors@0.2.0': dependencies: extendable-error: 0.1.7 - '@changesets/get-dependents-graph@1.3.5': + '@changesets/get-dependents-graph@2.1.2': dependencies: - '@changesets/types': 5.2.1 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - chalk: 2.4.2 - fs-extra: 7.0.1 - semver: 5.7.1 + picocolors: 1.1.1 + semver: 7.5.4 - '@changesets/get-release-plan@3.0.16': + '@changesets/get-release-plan@4.0.4': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/assemble-release-plan': 5.2.3 - '@changesets/config': 2.3.0 - '@changesets/pre': 1.0.14 - '@changesets/read': 0.5.9 - '@changesets/types': 5.2.1 + '@changesets/assemble-release-plan': 6.0.4 + '@changesets/config': 3.0.3 + '@changesets/pre': 2.0.1 + '@changesets/read': 0.6.1 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 - '@changesets/get-version-range-type@0.3.2': {} - - '@changesets/git@1.5.0': - dependencies: - '@babel/runtime': 7.21.0 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 - '@manypkg/get-packages': 1.1.3 - is-subdir: 1.2.0 - spawndamnit: 2.0.0 + '@changesets/get-version-range-type@0.4.0': {} - '@changesets/git@2.0.0': + '@changesets/git@3.0.1': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 + '@changesets/errors': 0.2.0 '@manypkg/get-packages': 1.1.3 is-subdir: 1.2.0 micromatch: 4.0.5 spawndamnit: 2.0.0 - '@changesets/logger@0.0.5': + '@changesets/logger@0.1.1': dependencies: - chalk: 2.4.2 + picocolors: 1.1.1 - '@changesets/parse@0.3.16': + '@changesets/parse@0.4.0': dependencies: - '@changesets/types': 5.2.1 + '@changesets/types': 6.0.0 js-yaml: 3.14.1 - '@changesets/pre@1.0.14': + '@changesets/pre@2.0.1': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/errors': 0.1.4 - '@changesets/types': 5.2.1 + '@changesets/errors': 0.2.0 + '@changesets/types': 6.0.0 '@manypkg/get-packages': 1.1.3 fs-extra: 7.0.1 - '@changesets/read@0.5.9': + '@changesets/read@0.6.1': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/git': 2.0.0 - '@changesets/logger': 0.0.5 - '@changesets/parse': 0.3.16 - '@changesets/types': 5.2.1 - chalk: 2.4.2 + '@changesets/git': 3.0.1 + '@changesets/logger': 0.1.1 + '@changesets/parse': 0.4.0 + '@changesets/types': 6.0.0 fs-extra: 7.0.1 p-filter: 2.1.0 + picocolors: 1.1.1 + + '@changesets/should-skip-package@0.1.1': + dependencies: + '@changesets/types': 6.0.0 + '@manypkg/get-packages': 1.1.3 '@changesets/types@4.1.0': {} - '@changesets/types@5.2.1': {} + '@changesets/types@6.0.0': {} - '@changesets/write@0.1.9': + '@changesets/write@0.3.2': dependencies: - '@babel/runtime': 7.21.0 - '@changesets/types': 5.2.1 + '@changesets/types': 6.0.0 fs-extra: 7.0.1 human-id: 1.0.2 - prettier: 1.19.1 + prettier: 2.7.1 '@clinic/bubbleprof@8.0.1': dependencies: @@ -10176,8 +9536,8 @@ snapshots: chalk: 4.1.2 lodash.debounce: 4.0.8 loose-envify: 1.4.0 - postcss: 8.4.23 - postcss-import: 13.0.0(postcss@8.4.23) + postcss: 8.4.47 + postcss-import: 13.0.0(postcss@8.4.47) stream-template: 0.0.10 webfontloader: 1.6.28 @@ -10259,10 +9619,6 @@ snapshots: dependencies: turbo-json-parse: 2.3.0 - '@cloudflare/kv-asset-handler@0.3.0': - dependencies: - mime: 3.0.0 - '@colors/colors@1.5.0': optional: true @@ -10407,72 +9763,213 @@ snapshots: transitivePeerDependencies: - typescript + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/aix-ppc64@0.23.1': + optional: true + '@esbuild/android-arm64@0.17.18': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.23.1': + optional: true + '@esbuild/android-arm@0.17.18': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-arm@0.23.1': + optional: true + '@esbuild/android-x64@0.17.18': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/android-x64@0.23.1': + optional: true + '@esbuild/darwin-arm64@0.17.18': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.23.1': + optional: true + '@esbuild/darwin-x64@0.17.18': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.23.1': + optional: true + '@esbuild/freebsd-arm64@0.17.18': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.23.1': + optional: true + '@esbuild/freebsd-x64@0.17.18': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.23.1': + optional: true + '@esbuild/linux-arm64@0.17.18': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.23.1': + optional: true + '@esbuild/linux-arm@0.17.18': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-arm@0.23.1': + optional: true + '@esbuild/linux-ia32@0.17.18': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.23.1': + optional: true + '@esbuild/linux-loong64@0.17.18': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.23.1': + optional: true + '@esbuild/linux-mips64el@0.17.18': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.23.1': + optional: true + '@esbuild/linux-ppc64@0.17.18': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.23.1': + optional: true + '@esbuild/linux-riscv64@0.17.18': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.23.1': + optional: true + '@esbuild/linux-s390x@0.17.18': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.23.1': + optional: true + '@esbuild/linux-x64@0.17.18': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/linux-x64@0.23.1': + optional: true + '@esbuild/netbsd-x64@0.17.18': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.23.1': + optional: true + + '@esbuild/openbsd-arm64@0.23.1': + optional: true + '@esbuild/openbsd-x64@0.17.18': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.23.1': + optional: true + '@esbuild/sunos-x64@0.17.18': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.23.1': + optional: true + '@esbuild/win32-arm64@0.17.18': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.23.1': + optional: true + '@esbuild/win32-ia32@0.17.18': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.23.1': + optional: true + '@esbuild/win32-x64@0.17.18': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + + '@esbuild/win32-x64@0.23.1': + optional: true + '@eslint-community/eslint-utils@4.4.0(eslint@7.32.0)': dependencies: eslint: 7.32.0 @@ -10690,10 +10187,10 @@ snapshots: '@types/ws': 8.5.4 '@whatwg-node/fetch': 0.8.8 graphql: 16.8.1 - isomorphic-ws: 5.0.0(ws@8.13.0) + isomorphic-ws: 5.0.0(ws@8.16.0) tslib: 2.5.0 value-or-promise: 1.0.11 - ws: 8.13.0 + ws: 8.16.0 transitivePeerDependencies: - '@types/node' - bufferutil @@ -10830,49 +10327,86 @@ snapshots: '@img/sharp-win32-x64@0.33.2': optional: true - '@ioredis/commands@1.2.0': {} + '@inquirer/confirm@5.0.0(@types/node@22.8.7)': + dependencies: + '@inquirer/core': 10.0.0(@types/node@22.8.7) + '@inquirer/type': 3.0.0(@types/node@22.8.7) + transitivePeerDependencies: + - '@types/node' + + '@inquirer/core@10.0.0(@types/node@22.8.7)': + dependencies: + '@inquirer/figures': 1.0.7 + '@inquirer/type': 3.0.0(@types/node@22.8.7) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' + + '@inquirer/figures@1.0.7': {} + + '@inquirer/type@3.0.0(@types/node@22.8.7)': + dependencies: + '@types/node': 22.8.7 '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.0 + strip-ansi: 7.0.1 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 + '@istanbuljs/schema@0.1.3': {} + '@jridgewell/gen-mapping@0.1.1': dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 '@jridgewell/gen-mapping@0.3.2': dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.14 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.19 '@jridgewell/gen-mapping@0.3.3': dependencies: '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.19 + '@jridgewell/gen-mapping@0.3.5': + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/resolve-uri@3.1.0': {} '@jridgewell/resolve-uri@3.1.1': {} '@jridgewell/set-array@1.1.2': {} + '@jridgewell/set-array@1.2.1': {} + '@jridgewell/source-map@0.3.3': dependencies: - '@jridgewell/gen-mapping': 0.3.2 - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/sourcemap-codec@1.4.14': {} '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.18': dependencies: '@jridgewell/resolve-uri': 3.1.0 @@ -10881,12 +10415,17 @@ snapshots: '@jridgewell/trace-mapping@0.3.19': dependencies: '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 + + '@jridgewell/trace-mapping@0.3.25': + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping@0.3.9': dependencies: '@jridgewell/resolve-uri': 3.1.0 - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.4.15 '@lit-labs/ssr-dom-shim@1.1.0': {} @@ -11028,21 +10567,6 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 - '@mapbox/node-pre-gyp@1.0.10(encoding@0.1.13)': - dependencies: - detect-libc: 2.0.1 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.6.9(encoding@0.1.13) - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.5.4 - tar: 6.1.13 - transitivePeerDependencies: - - encoding - - supports-color - '@mdn/browser-compat-data@3.3.14': {} '@mdn/browser-compat-data@4.2.1': {} @@ -11056,15 +10580,20 @@ snapshots: '@microsoft/tsdoc@0.13.2': {} + '@mswjs/interceptors@0.36.5': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + '@nearform/heap-profiler@2.0.0': dependencies: abort-controller: 3.0.0 sonic-boom: 1.4.1 - '@netlify/functions@1.4.0': - dependencies: - is-promise: 4.0.0 - '@next/env@13.3.0': {} '@next/eslint-plugin-next@12.1.0': @@ -11175,129 +10704,14 @@ snapshots: - bluebird - supports-color - '@nuxt/devalue@2.0.0': {} + '@open-draft/deferred-promise@2.2.0': {} - '@nuxt/kit@3.4.2(rollup@3.21.0)': + '@open-draft/logger@0.3.0': dependencies: - '@nuxt/schema': 3.4.2(rollup@3.21.0) - c12: 1.4.1 - consola: 3.1.0 - defu: 6.1.2 - globby: 13.1.4 - hash-sum: 2.0.0 - ignore: 5.2.4 - jiti: 1.18.2 - knitwork: 1.0.0 - lodash.template: 4.5.0 - mlly: 1.2.0 - pathe: 1.1.0 - pkg-types: 1.0.2 - scule: 1.0.0 - semver: 7.5.0 - unctx: 2.3.0 - unimport: 3.0.6(rollup@3.21.0) - untyped: 1.3.2 - transitivePeerDependencies: - - rollup - - supports-color + is-node-process: 1.2.0 + outvariant: 1.4.3 - '@nuxt/schema@3.4.2(rollup@3.21.0)': - dependencies: - defu: 6.1.2 - hookable: 5.5.3 - pathe: 1.1.0 - pkg-types: 1.0.2 - postcss-import-resolver: 2.0.0 - std-env: 3.3.2 - ufo: 1.1.1 - unimport: 3.0.6(rollup@3.21.0) - untyped: 1.3.2 - transitivePeerDependencies: - - rollup - - supports-color - - '@nuxt/telemetry@2.2.0(rollup@3.21.0)': - dependencies: - '@nuxt/kit': 3.4.2(rollup@3.21.0) - chalk: 5.2.0 - ci-info: 3.8.0 - consola: 3.1.0 - create-require: 1.1.1 - defu: 6.1.2 - destr: 1.2.2 - dotenv: 16.0.3 - fs-extra: 10.1.0 - git-url-parse: 13.1.0 - inquirer: 9.2.0 - is-docker: 3.0.0 - jiti: 1.18.2 - mri: 1.2.0 - nanoid: 4.0.2 - node-fetch: 3.3.1 - ofetch: 1.0.1 - parse-git-config: 3.0.0 - rc9: 2.1.0 - std-env: 3.3.2 - transitivePeerDependencies: - - rollup - - supports-color - - '@nuxt/ui-templates@1.1.1': {} - - '@nuxt/vite-builder@3.4.2(@types/node@18.0.6)(eslint@8.39.0)(optionator@0.9.1)(rollup@3.21.0)(terser@5.17.1)(typescript@5.6.3)(vue-tsc@2.1.10(typescript@5.6.3))(vue@3.2.47)': - dependencies: - '@nuxt/kit': 3.4.2(rollup@3.21.0) - '@rollup/plugin-replace': 5.0.2(rollup@3.21.0) - '@vitejs/plugin-vue': 4.2.0(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue@3.2.47) - '@vitejs/plugin-vue-jsx': 3.0.1(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue@3.2.47) - autoprefixer: 10.4.14(postcss@8.4.23) - clear: 0.1.0 - cssnano: 6.0.0(postcss@8.4.23) - defu: 6.1.2 - esbuild: 0.17.18 - escape-string-regexp: 5.0.0 - estree-walker: 3.0.3 - externality: 1.0.0 - fs-extra: 11.1.1 - get-port-please: 3.0.1 - h3: 1.6.4 - knitwork: 1.0.0 - magic-string: 0.30.0 - mlly: 1.2.0 - ohash: 1.1.2 - pathe: 1.1.0 - perfect-debounce: 0.1.3 - pkg-types: 1.0.2 - postcss: 8.4.23 - postcss-import: 15.1.0(postcss@8.4.23) - postcss-url: 10.1.3(postcss@8.4.23) - rollup-plugin-visualizer: 5.9.0(rollup@3.21.0) - std-env: 3.3.2 - strip-literal: 1.0.1 - ufo: 1.1.1 - unplugin: 1.3.1 - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) - vite-node: 0.30.1(@types/node@18.0.6)(terser@5.17.1) - vite-plugin-checker: 0.5.6(eslint@8.39.0)(optionator@0.9.1)(typescript@5.6.3)(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue-tsc@2.1.10(typescript@5.6.3)) - vue: 3.2.47 - vue-bundle-renderer: 1.0.3 - transitivePeerDependencies: - - '@types/node' - - eslint - - less - - meow - - optionator - - rollup - - sass - - stylelint - - stylus - - sugarss - - supports-color - - terser - - typescript - - vls - - vti - - vue-tsc + '@open-draft/until@2.1.0': {} '@peculiar/asn1-schema@2.3.6': dependencies: @@ -11332,6 +10746,8 @@ snapshots: '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 + '@polka/url@1.0.0-next.28': {} + '@prisma/prisma-fmt-wasm@3.10.0-50.73e60b76d394f8d37d8ebd1f8918c79029f0db86': {} '@puppeteer/browsers@1.9.1': @@ -11364,79 +10780,53 @@ snapshots: '@rgba-image/copy': 0.1.3 '@rgba-image/create-image': 0.1.1 - '@rollup/plugin-alias@5.0.0(rollup@3.21.0)': - dependencies: - slash: 4.0.0 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-android-arm-eabi@4.24.0': + optional: true - '@rollup/plugin-commonjs@24.1.0(rollup@3.21.0)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 8.1.0 - is-reference: 1.2.1 - magic-string: 0.27.0 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-android-arm64@4.24.0': + optional: true - '@rollup/plugin-inject@5.0.3(rollup@3.21.0)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - estree-walker: 2.0.2 - magic-string: 0.27.0 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-darwin-arm64@4.24.0': + optional: true - '@rollup/plugin-json@6.0.0(rollup@3.21.0)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-darwin-x64@4.24.0': + optional: true - '@rollup/plugin-node-resolve@15.0.2(rollup@3.21.0)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-builtin-module: 3.2.1 - is-module: 1.0.0 - resolve: 1.22.1 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-linux-arm-gnueabihf@4.24.0': + optional: true - '@rollup/plugin-replace@5.0.2(rollup@3.21.0)': - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - magic-string: 0.27.0 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-linux-arm-musleabihf@4.24.0': + optional: true - '@rollup/plugin-terser@0.4.1(rollup@3.21.0)': - dependencies: - serialize-javascript: 6.0.1 - smob: 0.0.6 - terser: 5.17.1 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-linux-arm64-gnu@4.24.0': + optional: true - '@rollup/plugin-wasm@6.1.2(rollup@3.21.0)': - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-linux-arm64-musl@4.24.0': + optional: true - '@rollup/pluginutils@4.2.1': - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 + '@rollup/rollup-linux-powerpc64le-gnu@4.24.0': + optional: true - '@rollup/pluginutils@5.0.2(rollup@3.21.0)': - dependencies: - '@types/estree': 1.0.0 - estree-walker: 2.0.2 - picomatch: 2.3.1 - optionalDependencies: - rollup: 3.21.0 + '@rollup/rollup-linux-riscv64-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.24.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.24.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.24.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.24.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.24.0': + optional: true '@rushstack/eslint-patch@1.1.0': {} @@ -11448,7 +10838,7 @@ snapshots: '@sitespeed.io/tracium@0.3.3': dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -11526,6 +10916,17 @@ snapshots: transitivePeerDependencies: - encoding + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.21.4 + '@babel/runtime': 7.21.0 + '@types/aria-query': 5.0.1 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + '@testing-library/dom@8.20.0': dependencies: '@babel/code-frame': 7.21.4 @@ -11537,6 +10938,10 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 + '@testing-library/user-event@14.5.2(@testing-library/dom@10.4.0)': + dependencies: + '@testing-library/dom': 10.4.0 + '@tokenizer/token@0.3.0': {} '@tootallnate/once@2.0.0': {} @@ -11563,6 +10968,8 @@ snapshots: dependencies: '@types/node': 18.0.6 + '@types/cookie@0.6.0': {} + '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 @@ -11587,6 +10994,8 @@ snapshots: '@types/estree@1.0.0': {} + '@types/estree@1.0.6': {} + '@types/extend@3.0.1': {} '@types/fs-extra@9.0.13': @@ -11595,13 +11004,9 @@ snapshots: '@types/hast@2.3.4': dependencies: - '@types/unist': 2.0.6 - - '@types/http-cache-semantics@4.0.1': {} + '@types/unist': 3.0.2 - '@types/is-ci@3.0.0': - dependencies: - ci-info: 3.3.2 + '@types/http-cache-semantics@4.0.1': {} '@types/is-empty@1.2.1': {} @@ -11641,6 +11046,10 @@ snapshots: '@types/node@18.0.6': {} + '@types/node@22.8.7': + dependencies: + undici-types: 6.19.8 + '@types/normalize-package-data@2.4.1': {} '@types/offscreencanvas@2019.3.0': {} @@ -11665,8 +11074,6 @@ snapshots: '@types/scheduler': 0.16.3 csstype: 3.1.2 - '@types/resolve@1.20.2': {} - '@types/responselike@1.0.0': dependencies: '@types/node': 18.0.6 @@ -11675,13 +11082,13 @@ snapshots: '@types/seedrandom@2.4.30': {} - '@types/semver@6.2.3': {} - '@types/semver@7.3.13': {} '@types/sharp@0.31.1': dependencies: - '@types/node': 18.0.6 + '@types/node': 22.8.7 + + '@types/statuses@2.0.5': {} '@types/supports-color@8.1.1': {} @@ -11691,6 +11098,8 @@ snapshots: '@types/text-table@0.2.2': {} + '@types/tough-cookie@4.0.5': {} + '@types/trusted-types@2.0.3': {} '@types/unist@2.0.6': {} @@ -11797,7 +11206,7 @@ snapshots: '@typescript-eslint/type-utils@5.13.0(eslint@7.32.0)(typescript@4.7.4)': dependencies: '@typescript-eslint/utils': 5.13.0(eslint@7.32.0)(typescript@4.7.4) - debug: 4.3.4 + debug: 4.3.7 eslint: 7.32.0 tsutils: 3.21.0(typescript@4.7.4) optionalDependencies: @@ -11827,7 +11236,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.13.0 '@typescript-eslint/visitor-keys': 5.13.0 - debug: 4.3.4 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -11869,7 +11278,7 @@ snapshots: dependencies: '@typescript-eslint/types': 5.59.8 '@typescript-eslint/visitor-keys': 5.59.8 - debug: 4.3.4 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -11952,74 +11361,103 @@ snapshots: '@typescript-eslint/types': 5.59.8 eslint-visitor-keys: 3.4.0 - '@unhead/dom@1.1.26': + '@vitejs/plugin-react@4.0.0(vite@4.3.2(@types/node@22.8.7)(terser@5.17.1))': dependencies: - '@unhead/schema': 1.1.26 - '@unhead/shared': 1.1.26 + '@babel/core': 7.21.4 + '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.4) + '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.4) + react-refresh: 0.14.0 + vite: 4.3.2(@types/node@22.8.7)(terser@5.17.1) + transitivePeerDependencies: + - supports-color - '@unhead/schema@1.1.26': + '@vitejs/plugin-vue@4.2.0(vite@4.3.2(@types/node@22.8.7)(terser@5.17.1))(vue@3.5.12(typescript@5.6.3))': dependencies: - hookable: 5.5.3 - zhead: 2.0.4 + vite: 4.3.2(@types/node@22.8.7)(terser@5.17.1) + vue: 3.5.12(typescript@5.6.3) - '@unhead/shared@1.1.26': + '@vitest/browser@2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3)': dependencies: - '@unhead/schema': 1.1.26 + '@testing-library/dom': 10.4.0 + '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1)) + '@vitest/utils': 2.1.3 + magic-string: 0.30.12 + msw: 2.5.1(@types/node@22.8.7)(typescript@4.7.4) + sirv: 2.0.4 + tinyrainbow: 1.2.0 + vitest: 2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1) + ws: 8.18.0 + optionalDependencies: + playwright: 1.48.2 + transitivePeerDependencies: + - '@types/node' + - '@vitest/spy' + - bufferutil + - typescript + - utf-8-validate + - vite + + '@vitest/coverage-v8@2.1.3(@vitest/browser@2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3))(vitest@2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.1.7 + magic-string: 0.30.12 + magicast: 0.3.5 + std-env: 3.7.0 + test-exclude: 7.0.1 + tinyrainbow: 1.2.0 + vitest: 2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1) + optionalDependencies: + '@vitest/browser': 2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3) + transitivePeerDependencies: + - supports-color - '@unhead/ssr@1.1.26': + '@vitest/expect@2.1.3': dependencies: - '@unhead/schema': 1.1.26 - '@unhead/shared': 1.1.26 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 + chai: 5.1.2 + tinyrainbow: 1.2.0 - '@unhead/vue@1.1.26(vue@3.2.47)': + '@vitest/mocker@2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))': dependencies: - '@unhead/schema': 1.1.26 - '@unhead/shared': 1.1.26 - hookable: 5.5.3 - unhead: 1.1.26 - vue: 3.2.47 + '@vitest/spy': 2.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.12 + optionalDependencies: + msw: 2.5.1(@types/node@22.8.7)(typescript@4.7.4) + vite: 5.4.10(@types/node@22.8.7)(terser@5.17.1) - '@vercel/nft@0.22.6(encoding@0.1.13)': + '@vitest/pretty-format@2.1.3': dependencies: - '@mapbox/node-pre-gyp': 1.0.10(encoding@0.1.13) - '@rollup/pluginutils': 4.2.1 - acorn: 8.8.2 - async-sema: 3.1.1 - bindings: 1.5.0 - estree-walker: 2.0.2 - glob: 7.2.3 - graceful-fs: 4.2.11 - micromatch: 4.0.5 - node-gyp-build: 4.6.0 - resolve-from: 5.0.0 - transitivePeerDependencies: - - encoding - - supports-color + tinyrainbow: 1.2.0 - '@vitejs/plugin-react@4.0.0(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))': + '@vitest/runner@2.1.3': dependencies: - '@babel/core': 7.21.4 - '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.4) - '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.4) - react-refresh: 0.14.0 - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) - transitivePeerDependencies: - - supports-color + '@vitest/utils': 2.1.3 + pathe: 1.1.2 - '@vitejs/plugin-vue-jsx@3.0.1(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue@3.2.47)': + '@vitest/snapshot@2.1.3': dependencies: - '@babel/core': 7.21.4 - '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.4) - '@vue/babel-plugin-jsx': 1.1.1(@babel/core@7.21.4) - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) - vue: 3.2.47 - transitivePeerDependencies: - - supports-color + '@vitest/pretty-format': 2.1.3 + magic-string: 0.30.12 + pathe: 1.1.2 + + '@vitest/spy@2.1.3': + dependencies: + tinyspy: 3.0.2 - '@vitejs/plugin-vue@4.2.0(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue@3.2.47)': + '@vitest/utils@2.1.3': dependencies: - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) - vue: 3.2.47 + '@vitest/pretty-format': 2.1.3 + loupe: 3.1.2 + tinyrainbow: 1.2.0 '@volar/language-core@2.4.10': dependencies: @@ -12033,43 +11471,14 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@vue/babel-helper-vue-transform-on@1.0.2': {} - - '@vue/babel-plugin-jsx@1.1.1(@babel/core@7.21.4)': - dependencies: - '@babel/helper-module-imports': 7.21.4 - '@babel/plugin-syntax-jsx': 7.21.4(@babel/core@7.21.4) - '@babel/template': 7.20.7 - '@babel/traverse': 7.21.4 - '@babel/types': 7.21.4 - '@vue/babel-helper-vue-transform-on': 1.0.2 - camelcase: 6.3.0 - html-tags: 3.3.1 - svg-tags: 1.0.0 - transitivePeerDependencies: - - '@babel/core' - - supports-color - - '@vue/compiler-core@3.2.47': - dependencies: - '@babel/parser': 7.21.4 - '@vue/shared': 3.2.47 - estree-walker: 2.0.2 - source-map: 0.6.1 - '@vue/compiler-core@3.5.12': dependencies: - '@babel/parser': 7.26.2 + '@babel/parser': 7.25.9 '@vue/shared': 3.5.12 entities: 4.5.0 estree-walker: 2.0.2 source-map-js: 1.2.1 - '@vue/compiler-dom@3.2.47': - dependencies: - '@vue/compiler-core': 3.2.47 - '@vue/shared': 3.2.47 - '@vue/compiler-dom@3.5.12': dependencies: '@vue/compiler-core': 3.5.12 @@ -12081,31 +11490,28 @@ snapshots: postcss: 8.4.23 source-map: 0.6.1 - '@vue/compiler-sfc@3.2.47': + '@vue/compiler-sfc@3.5.12': dependencies: - '@babel/parser': 7.21.4 - '@vue/compiler-core': 3.2.47 - '@vue/compiler-dom': 3.2.47 - '@vue/compiler-ssr': 3.2.47 - '@vue/reactivity-transform': 3.2.47 - '@vue/shared': 3.2.47 + '@babel/parser': 7.25.9 + '@vue/compiler-core': 3.5.12 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 estree-walker: 2.0.2 - magic-string: 0.25.9 - postcss: 8.4.23 - source-map: 0.6.1 + magic-string: 0.30.12 + postcss: 8.4.47 + source-map-js: 1.2.1 - '@vue/compiler-ssr@3.2.47': + '@vue/compiler-ssr@3.5.12': dependencies: - '@vue/compiler-dom': 3.2.47 - '@vue/shared': 3.2.47 + '@vue/compiler-dom': 3.5.12 + '@vue/shared': 3.5.12 '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 he: 1.2.0 - '@vue/devtools-api@6.5.0': {} - '@vue/language-core@2.1.10(typescript@5.6.3)': dependencies: '@volar/language-core': 2.4.10 @@ -12113,42 +11519,33 @@ snapshots: '@vue/compiler-vue2': 2.7.16 '@vue/shared': 3.5.12 alien-signals: 0.2.2 - minimatch: 9.0.3 + minimatch: 9.0.5 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: typescript: 5.6.3 - '@vue/reactivity-transform@3.2.47': - dependencies: - '@babel/parser': 7.21.4 - '@vue/compiler-core': 3.2.47 - '@vue/shared': 3.2.47 - estree-walker: 2.0.2 - magic-string: 0.25.9 - - '@vue/reactivity@3.2.47': + '@vue/reactivity@3.5.12': dependencies: - '@vue/shared': 3.2.47 + '@vue/shared': 3.5.12 - '@vue/runtime-core@3.2.47': + '@vue/runtime-core@3.5.12': dependencies: - '@vue/reactivity': 3.2.47 - '@vue/shared': 3.2.47 + '@vue/reactivity': 3.5.12 + '@vue/shared': 3.5.12 - '@vue/runtime-dom@3.2.47': + '@vue/runtime-dom@3.5.12': dependencies: - '@vue/runtime-core': 3.2.47 - '@vue/shared': 3.2.47 - csstype: 2.6.21 + '@vue/reactivity': 3.5.12 + '@vue/runtime-core': 3.5.12 + '@vue/shared': 3.5.12 + csstype: 3.1.3 - '@vue/server-renderer@3.2.47(vue@3.2.47)': + '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3))': dependencies: - '@vue/compiler-ssr': 3.2.47 - '@vue/shared': 3.2.47 - vue: 3.2.47 - - '@vue/shared@3.2.47': {} + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 + vue: 3.5.12(typescript@5.6.3) '@vue/shared@3.5.12': {} @@ -12265,11 +11662,6 @@ snapshots: dependencies: event-target-shim: 5.0.1 - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - acorn-import-assertions@1.9.0(acorn@8.8.2): dependencies: acorn: 8.8.2 @@ -12298,19 +11690,19 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color agent-base@7.1.0: dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color agentkeepalive@4.2.1: dependencies: - debug: 4.3.4 + debug: 4.3.7 depd: 1.1.2 humanize-ms: 1.2.1 transitivePeerDependencies: @@ -12357,10 +11749,6 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@6.2.0: - dependencies: - type-fest: 3.9.0 - ansi-regex@2.1.1: {} ansi-regex@3.0.1: {} @@ -12396,36 +11784,6 @@ snapshots: aproba@2.0.0: {} - arch@2.2.0: {} - - archiver-utils@2.1.0: - dependencies: - glob: 7.2.3 - graceful-fs: 4.2.11 - lazystream: 1.0.1 - lodash.defaults: 4.2.0 - lodash.difference: 4.5.0 - lodash.flatten: 4.4.0 - lodash.isplainobject: 4.0.6 - lodash.union: 4.6.0 - normalize-path: 3.0.0 - readable-stream: 2.3.8 - - archiver@5.3.1: - dependencies: - archiver-utils: 2.1.0 - async: 3.2.4 - buffer-crc32: 0.2.13 - readable-stream: 3.6.1 - readdir-glob: 1.1.3 - tar-stream: 2.2.0 - zip-stream: 4.1.0 - - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.1 - are-we-there-yet@3.0.1: dependencies: delegates: 1.0.0 @@ -12448,13 +11806,15 @@ snapshots: dependencies: deep-equal: 2.2.1 + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + array-buffer-byte-length@1.0.0: dependencies: call-bind: 1.0.2 is-array-buffer: 3.0.2 - array-flatten@1.1.1: {} - array-flatten@3.0.0: {} array-from@2.1.1: {} @@ -12511,6 +11871,8 @@ snapshots: object-assign: 4.1.1 util: 0.10.3 + assertion-error@2.0.1: {} + ast-metadata-inferer@0.7.0: dependencies: '@mdn/browser-compat-data': 3.3.14 @@ -12523,8 +11885,6 @@ snapshots: astral-regex@2.0.0: {} - async-sema@3.1.1: {} - async@2.6.4: dependencies: lodash: 4.17.21 @@ -12567,16 +11927,6 @@ snapshots: subarg: 1.0.0 timestring: 6.0.0 - autoprefixer@10.4.14(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - caniuse-lite: 1.0.30001480 - fraction.js: 4.2.0 - normalize-range: 0.1.2 - picocolors: 1.0.0 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - available-typed-arrays@1.0.5: {} aws-sign2@0.7.0: {} @@ -12611,10 +11961,6 @@ snapshots: binary-extensions@2.2.0: {} - bindings@1.5.0: - dependencies: - file-uri-to-path: 1.0.0 - bit-twiddle@1.0.2: {} bl@4.1.0: @@ -12623,33 +11969,10 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.1 - bl@5.1.0: - dependencies: - buffer: 6.0.3 - inherits: 2.0.4 - readable-stream: 3.6.1 - bn.js@4.12.0: {} bn.js@5.2.1: {} - body-parser@1.20.1: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.1 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - boolbase@1.0.0: {} boxen@5.1.2: @@ -12687,10 +12010,6 @@ snapshots: dependencies: fill-range: 7.0.1 - breakword@1.0.5: - dependencies: - wcwidth: 1.0.1 - brfs@2.0.2: dependencies: quote-stream: 1.0.2 @@ -12840,13 +12159,6 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - builtin-modules@3.3.0: {} - builtin-status-codes@3.0.0: {} builtins@4.1.0: @@ -12857,9 +12169,9 @@ snapshots: dependencies: semver: 7.5.4 - bundle-require@3.1.2(esbuild@0.14.49): + bundle-require@5.0.0(esbuild@0.23.1): dependencies: - esbuild: 0.14.49 + esbuild: 0.23.1 load-tsconfig: 0.2.5 busboy@1.6.0: @@ -12868,24 +12180,6 @@ snapshots: bytes-iec@3.1.1: {} - bytes@3.1.2: {} - - c12@1.4.1: - dependencies: - chokidar: 3.5.3 - defu: 6.1.2 - dotenv: 16.0.3 - giget: 1.1.2 - jiti: 1.18.2 - mlly: 1.2.0 - ohash: 1.1.2 - pathe: 1.1.0 - perfect-debounce: 0.1.3 - pkg-types: 1.0.2 - rc9: 2.1.0 - transitivePeerDependencies: - - supports-color - cac@6.7.14: {} cacache@16.1.3: @@ -12959,13 +12253,6 @@ snapshots: camelcase@7.0.1: {} - caniuse-api@3.0.0: - dependencies: - browserslist: 4.21.5 - caniuse-lite: 1.0.30001480 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001480: {} case-anything@2.1.10: {} @@ -12976,6 +12263,14 @@ snapshots: cephes@1.2.0: {} + chai@5.1.2: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 + chalk@1.1.3: dependencies: ansi-styles: 2.2.1 @@ -13009,6 +12304,8 @@ snapshots: chardet@0.7.0: {} + check-error@2.1.1: {} + chokidar@3.5.3: dependencies: anymatch: 3.1.3 @@ -13021,6 +12318,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.2 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + chownr@1.1.4: {} chownr@2.0.0: {} @@ -13035,8 +12344,6 @@ snapshots: ci-info@2.0.0: {} - ci-info@3.3.2: {} - ci-info@3.8.0: {} cipher-base@1.0.4: @@ -13046,8 +12353,6 @@ snapshots: clean-stack@2.2.0: {} - clear@0.1.0: {} - cli-boxes@2.2.1: {} cli-boxes@3.0.0: {} @@ -13060,10 +12365,6 @@ snapshots: dependencies: restore-cursor: 3.1.0 - cli-cursor@4.0.0: - dependencies: - restore-cursor: 4.0.0 - cli-spinners@2.7.0: {} cli-table3@0.6.3: @@ -13088,7 +12389,7 @@ snapshots: cli-width@2.2.1: {} - cli-width@4.0.0: {} + cli-width@4.1.0: {} client-only@0.0.1: {} @@ -13120,24 +12421,12 @@ snapshots: clipboard-copy@4.0.1: {} - clipboardy@3.0.0: - dependencies: - arch: 2.2.0 - execa: 5.1.1 - is-wsl: 2.2.0 - cliui@5.0.0: dependencies: string-width: 3.1.0 strip-ansi: 5.2.0 wrap-ansi: 5.1.0 - cliui@6.0.0: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -13156,8 +12445,6 @@ snapshots: clone@1.0.4: {} - cluster-key-slot@1.1.2: {} - code-point-at@1.1.0: {} color-convert@1.9.3: @@ -13184,8 +12471,6 @@ snapshots: color-convert: 2.0.1 color-string: 1.9.1 - colord@2.9.3: {} - colorette@2.0.19: {} colors@1.0.3: {} @@ -13220,20 +12505,11 @@ snapshots: leven: 2.1.0 minimist: 1.2.8 - commondir@1.0.1: {} - compare-func@2.0.0: dependencies: array-ify: 1.0.0 dot-prop: 5.3.0 - compress-commons@4.1.1: - dependencies: - buffer-crc32: 0.2.13 - crc32-stream: 4.0.2 - normalize-path: 3.0.0 - readable-stream: 3.6.1 - concat-map@0.0.1: {} concat-stream@1.6.2: @@ -13287,7 +12563,7 @@ snapshots: confusing-browser-globals@1.0.11: {} - consola@3.1.0: {} + consola@3.2.3: {} console-browserify@1.2.0: {} @@ -13295,12 +12571,6 @@ snapshots: constants-browserify@1.0.0: {} - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - conventional-changelog-angular@5.0.13: dependencies: compare-func: 2.0.0 @@ -13329,10 +12599,6 @@ snapshots: convert-source-map@1.9.0: {} - cookie-es@0.5.0: {} - - cookie-signature@1.0.6: {} - cookie@0.5.0: {} copy-to-clipboard@3.3.3: @@ -13382,13 +12648,6 @@ snapshots: optionalDependencies: typescript: 4.7.4 - crc-32@1.2.2: {} - - crc32-stream@4.0.2: - dependencies: - crc-32: 1.2.2 - readable-stream: 3.6.1 - create-ecdh@4.0.4: dependencies: bn.js: 4.12.0 @@ -13465,10 +12724,6 @@ snapshots: dependencies: type-fest: 1.4.0 - css-declaration-sorter@6.4.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -13477,102 +12732,20 @@ snapshots: domutils: 2.8.0 nth-check: 2.1.1 - css-select@5.1.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.1.0 - domhandler: 5.0.3 - domutils: 3.0.1 - nth-check: 2.1.1 - css-tree@1.1.3: dependencies: mdn-data: 2.0.14 source-map: 0.6.1 - css-tree@2.2.1: - dependencies: - mdn-data: 2.0.28 - source-map-js: 1.0.2 - - css-tree@2.3.1: - dependencies: - mdn-data: 2.0.30 - source-map-js: 1.0.2 - css-what@6.1.0: {} - cssesc@3.0.0: {} - - cssnano-preset-default@6.0.0(postcss@8.4.23): - dependencies: - css-declaration-sorter: 6.4.0(postcss@8.4.23) - cssnano-utils: 4.0.0(postcss@8.4.23) - postcss: 8.4.23 - postcss-calc: 8.2.4(postcss@8.4.23) - postcss-colormin: 6.0.0(postcss@8.4.23) - postcss-convert-values: 6.0.0(postcss@8.4.23) - postcss-discard-comments: 6.0.0(postcss@8.4.23) - postcss-discard-duplicates: 6.0.0(postcss@8.4.23) - postcss-discard-empty: 6.0.0(postcss@8.4.23) - postcss-discard-overridden: 6.0.0(postcss@8.4.23) - postcss-merge-longhand: 6.0.0(postcss@8.4.23) - postcss-merge-rules: 6.0.0(postcss@8.4.23) - postcss-minify-font-values: 6.0.0(postcss@8.4.23) - postcss-minify-gradients: 6.0.0(postcss@8.4.23) - postcss-minify-params: 6.0.0(postcss@8.4.23) - postcss-minify-selectors: 6.0.0(postcss@8.4.23) - postcss-normalize-charset: 6.0.0(postcss@8.4.23) - postcss-normalize-display-values: 6.0.0(postcss@8.4.23) - postcss-normalize-positions: 6.0.0(postcss@8.4.23) - postcss-normalize-repeat-style: 6.0.0(postcss@8.4.23) - postcss-normalize-string: 6.0.0(postcss@8.4.23) - postcss-normalize-timing-functions: 6.0.0(postcss@8.4.23) - postcss-normalize-unicode: 6.0.0(postcss@8.4.23) - postcss-normalize-url: 6.0.0(postcss@8.4.23) - postcss-normalize-whitespace: 6.0.0(postcss@8.4.23) - postcss-ordered-values: 6.0.0(postcss@8.4.23) - postcss-reduce-initial: 6.0.0(postcss@8.4.23) - postcss-reduce-transforms: 6.0.0(postcss@8.4.23) - postcss-svgo: 6.0.0(postcss@8.4.23) - postcss-unique-selectors: 6.0.0(postcss@8.4.23) - - cssnano-utils@4.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - - cssnano@6.0.0(postcss@8.4.23): - dependencies: - cssnano-preset-default: 6.0.0(postcss@8.4.23) - lilconfig: 2.1.0 - postcss: 8.4.23 - csso@4.2.0: dependencies: css-tree: 1.1.3 - csso@5.0.5: - dependencies: - css-tree: 2.2.1 - - csstype@2.6.21: {} - csstype@3.1.2: {} - csv-generate@3.4.3: {} - - csv-parse@4.16.3: {} - - csv-stringify@5.6.5: {} - - csv@5.5.3: - dependencies: - csv-generate: 3.4.3 - csv-parse: 4.16.3 - csv-stringify: 5.6.5 - stream-transform: 2.1.3 - - cuint@0.2.2: {} + csstype@3.1.3: {} cwise-compiler@1.1.3: dependencies: @@ -13967,6 +13140,10 @@ snapshots: dependencies: ms: 2.1.2 + debug@4.3.7: + dependencies: + ms: 2.1.3 + decamelize-keys@1.1.0: dependencies: decamelize: 1.2.0 @@ -13988,6 +13165,8 @@ snapshots: dependencies: mimic-response: 3.1.0 + deep-eql@5.0.2: {} + deep-equal@2.2.1: dependencies: array-buffer-byte-length: 1.0.0 @@ -14013,8 +13192,6 @@ snapshots: deep-is@0.1.4: {} - deepmerge@4.3.1: {} - defaults@1.0.3: dependencies: clone: 1.0.4 @@ -14023,8 +13200,6 @@ snapshots: defer-to-connect@2.0.1: {} - define-lazy-prop@2.0.0: {} - define-properties@1.2.0: dependencies: has-property-descriptors: 1.0.0 @@ -14032,8 +13207,6 @@ snapshots: defined@1.0.1: {} - defu@6.1.2: {} - degenerator@5.0.1: dependencies: ast-types: 0.13.4 @@ -14048,12 +13221,8 @@ snapshots: delegates@1.0.0: {} - denque@2.1.0: {} - depd@1.1.2: {} - depd@2.0.0: {} - deps-sort@2.0.1: dependencies: JSONStream: 1.3.5 @@ -14068,14 +13237,8 @@ snapshots: inherits: 2.0.4 minimalistic-assert: 1.0.1 - destr@1.2.2: {} - - destroy@1.2.0: {} - detect-indent@6.1.0: {} - detect-libc@2.0.1: {} - detect-libc@2.0.2: {} detective@5.2.1: @@ -14084,8 +13247,6 @@ snapshots: defined: 1.0.1 minimist: 1.2.8 - devalue@4.3.0: {} - devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -14128,12 +13289,6 @@ snapshots: domhandler: 4.3.1 entities: 2.2.0 - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - domain-browser@1.2.0: {} domelementtype@2.3.0: {} @@ -14142,10 +13297,6 @@ snapshots: dependencies: domelementtype: 2.3.0 - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - dompurify@2.3.5: {} domutils@2.8.0: @@ -14154,12 +13305,6 @@ snapshots: domelementtype: 2.3.0 domhandler: 4.3.1 - domutils@3.0.1: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - dot-prop@5.3.0: dependencies: is-obj: 2.0.0 @@ -14168,12 +13313,6 @@ snapshots: dependencies: is-obj: 2.0.0 - dot-prop@7.2.0: - dependencies: - type-fest: 2.19.0 - - dotenv@16.0.3: {} - dset@3.1.2: {} dup@1.0.0: {} @@ -14200,12 +13339,6 @@ snapshots: jsbn: 0.1.1 safer-buffer: 2.1.2 - ee-first@1.1.1: {} - - ejs@3.1.9: - dependencies: - jake: 10.8.7 - electron-to-chromium@1.4.371: {} elliptic@6.5.4: @@ -14224,8 +13357,6 @@ snapshots: emoji-regex@9.2.2: {} - encodeurl@1.0.2: {} - encoding@0.1.13: dependencies: iconv-lite: 0.6.3 @@ -14239,17 +13370,6 @@ snapshots: dependencies: inherits: 2.0.4 - enhanced-resolve@4.5.0: - dependencies: - graceful-fs: 4.2.11 - memory-fs: 0.5.0 - tapable: 1.1.3 - - enhanced-resolve@5.13.0: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.2.1 - enhanced-resolve@5.15.0: dependencies: graceful-fs: 4.2.11 @@ -14271,10 +13391,6 @@ snapshots: err-code@2.0.3: {} - errno@0.1.8: - dependencies: - prr: 1.0.1 - error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -14493,14 +13609,65 @@ snapshots: '@esbuild/win32-ia32': 0.17.18 '@esbuild/win32-x64': 0.17.18 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + + esbuild@0.23.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.1 + '@esbuild/android-arm': 0.23.1 + '@esbuild/android-arm64': 0.23.1 + '@esbuild/android-x64': 0.23.1 + '@esbuild/darwin-arm64': 0.23.1 + '@esbuild/darwin-x64': 0.23.1 + '@esbuild/freebsd-arm64': 0.23.1 + '@esbuild/freebsd-x64': 0.23.1 + '@esbuild/linux-arm': 0.23.1 + '@esbuild/linux-arm64': 0.23.1 + '@esbuild/linux-ia32': 0.23.1 + '@esbuild/linux-loong64': 0.23.1 + '@esbuild/linux-mips64el': 0.23.1 + '@esbuild/linux-ppc64': 0.23.1 + '@esbuild/linux-riscv64': 0.23.1 + '@esbuild/linux-s390x': 0.23.1 + '@esbuild/linux-x64': 0.23.1 + '@esbuild/netbsd-x64': 0.23.1 + '@esbuild/openbsd-arm64': 0.23.1 + '@esbuild/openbsd-x64': 0.23.1 + '@esbuild/sunos-x64': 0.23.1 + '@esbuild/win32-arm64': 0.23.1 + '@esbuild/win32-ia32': 0.23.1 + '@esbuild/win32-x64': 0.23.1 + escalade@3.1.1: {} escape-goat@2.1.1: {} escape-goat@4.0.0: {} - escape-html@1.0.3: {} - escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} @@ -14930,8 +14097,6 @@ snapshots: esutils@2.0.3: {} - etag@1.8.1: {} - event-emitter@0.3.5: dependencies: d: 1.0.1 @@ -14949,8 +14114,6 @@ snapshots: event-target-shim@5.0.1: {} - eventemitter3@4.0.7: {} - events@3.3.0: {} evp_bytestokey@1.0.3: @@ -14994,60 +14157,12 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 3.0.0 - execa@7.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 4.3.1 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.1.0 - onetime: 6.0.0 - signal-exit: 3.0.7 - strip-final-newline: 3.0.0 - execspawn@1.0.1: dependencies: util-extend: 1.0.3 expect-more@1.2.0: {} - express@4.18.2: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.1 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.5.0 - cookie-signature: 1.0.6 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.2.0 - fresh: 0.5.2 - http-errors: 2.0.0 - merge-descriptors: 1.0.1 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.7 - proxy-addr: 2.0.7 - qs: 6.11.0 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 - setprototypeof: 1.2.0 - statuses: 2.0.1 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - ext@1.7.0: dependencies: type: 2.7.2 @@ -15062,18 +14177,11 @@ snapshots: iconv-lite: 0.4.24 tmp: 0.0.33 - externality@1.0.0: - dependencies: - enhanced-resolve: 5.13.0 - mlly: 1.2.0 - pathe: 1.1.0 - ufo: 1.1.1 - extract-files@11.0.0: {} extract-zip@2.0.1: dependencies: - debug: 4.3.4 + debug: 4.3.7 get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -15127,6 +14235,10 @@ snapshots: dependencies: pend: 1.2.0 + fdir@6.4.2(picomatch@4.0.2): + optionalDependencies: + picomatch: 4.0.2 + fetch-blob@3.2.0: dependencies: node-domexception: 1.0.0 @@ -15140,11 +14252,6 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 - figures@5.0.0: - dependencies: - escape-string-regexp: 5.0.0 - is-unicode-supported: 1.3.0 - file-entry-cache@6.0.1: dependencies: flat-cache: 3.0.4 @@ -15156,28 +14263,10 @@ snapshots: token-types: 6.0.0 uint8array-extras: 1.4.0 - file-uri-to-path@1.0.0: {} - - filelist@1.0.4: - dependencies: - minimatch: 5.1.6 - fill-range@7.0.1: dependencies: to-regex-range: 5.0.1 - finalhandler@1.2.0: - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - find-chrome-bin@0.1.0: {} find-up@3.0.0: @@ -15194,11 +14283,6 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-yarn-workspace-root2@1.2.16: - dependencies: - micromatch: 4.0.5 - pkg-dir: 4.2.0 - flame-gradient@1.0.0: dependencies: sinusoidal-decimal: 1.0.0 @@ -15208,19 +14292,15 @@ snapshots: flatted: 3.2.7 rimraf: 3.0.2 - flat@5.0.2: {} - flatstr@1.0.12: {} flatted@3.2.7: {} - follow-redirects@1.15.2: {} - for-each@0.3.3: dependencies: is-callable: 1.2.7 - foreground-child@3.1.1: + foreground-child@3.3.0: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 @@ -15247,16 +14327,10 @@ snapshots: dependencies: fetch-blob: 3.2.0 - forwarded@0.2.0: {} - fp-and-or@0.1.3: {} fp-ts@2.12.1: {} - fraction.js@4.2.0: {} - - fresh@0.5.2: {} - from2-string@1.1.0: dependencies: from2: 2.3.0 @@ -15278,19 +14352,19 @@ snapshots: fs-extra@11.1.1: dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jsonfile: 6.1.0 universalify: 2.0.0 fs-extra@7.0.1: dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jsonfile: 4.0.0 universalify: 0.1.2 fs-extra@8.1.0: dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 jsonfile: 4.0.0 universalify: 0.1.2 @@ -15307,6 +14381,9 @@ snapshots: fs.realpath@1.0.0: {} + fsevents@2.3.2: + optional: true + fsevents@2.3.3: optional: true @@ -15323,18 +14400,6 @@ snapshots: functions-have-names@1.2.3: {} - gauge@3.0.2: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - gauge@4.0.4: dependencies: aproba: 2.0.0 @@ -15366,8 +14431,6 @@ snapshots: has: 1.0.3 has-symbols: 1.0.3 - get-port-please@3.0.1: {} - get-stdin@8.0.0: {} get-stream@4.1.0: @@ -15394,7 +14457,7 @@ snapshots: dependencies: basic-ftp: 5.0.4 data-uri-to-buffer: 6.0.1 - debug: 4.3.4 + debug: 4.3.7 fs-extra: 8.1.0 transitivePeerDependencies: - supports-color @@ -15403,20 +14466,6 @@ snapshots: dependencies: assert-plus: 1.0.0 - giget@1.1.2: - dependencies: - colorette: 2.0.19 - defu: 6.1.2 - https-proxy-agent: 5.0.1 - mri: 1.2.0 - node-fetch-native: 1.1.0 - pathe: 1.1.0 - tar: 6.1.13 - transitivePeerDependencies: - - supports-color - - git-config-path@2.0.0: {} - git-raw-commits@2.0.11: dependencies: dargs: 7.0.0 @@ -15425,15 +14474,6 @@ snapshots: split2: 3.2.2 through2: 4.0.2 - git-up@7.0.0: - dependencies: - is-ssh: 1.4.0 - parse-url: 8.1.0 - - git-url-parse@13.1.0: - dependencies: - git-up: 7.0.0 - github-slugger@1.5.0: {} github-slugger@2.0.0: {} @@ -15448,22 +14488,14 @@ snapshots: glob-to-regexp@0.4.1: {} - glob@10.3.4: - dependencies: - foreground-child: 3.1.1 - jackspeak: 2.3.1 - minimatch: 9.0.3 - minipass: 7.0.3 - path-scurry: 1.10.1 - - glob@7.1.6: + glob@10.4.5: dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.2 - once: 1.4.0 - path-is-absolute: 1.0.1 + foreground-child: 3.3.0 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 glob@7.1.7: dependencies: @@ -15610,20 +14642,6 @@ snapshots: graphql@16.8.1: {} - gzip-size@7.0.0: - dependencies: - duplexer: 0.1.2 - - h3@1.6.4: - dependencies: - cookie-es: 0.5.0 - defu: 6.1.2 - destr: 1.2.2 - iron-webcrypto: 0.6.0 - radix3: 1.0.1 - ufo: 1.1.1 - uncrypto: 0.1.2 - har-schema@2.0.0: {} har-validator@5.1.5: @@ -15673,8 +14691,6 @@ snapshots: readable-stream: 3.6.1 safe-buffer: 5.2.1 - hash-sum@2.0.0: {} - hash.js@1.1.7: dependencies: inherits: 2.0.4 @@ -15709,6 +14725,8 @@ snapshots: he@1.2.0: {} + headers-polyfill@4.0.3: {} + hidden-markov-model-tf@4.0.0(@tensorflow/tfjs-core@3.21.0(encoding@0.1.13)): dependencies: '@tensorflow/tfjs-core': 3.21.0(encoding@0.1.13) @@ -15726,8 +14744,6 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - hookable@5.5.3: {} - hosted-git-info@2.8.9: {} hosted-git-info@4.1.0: @@ -15740,7 +14756,7 @@ snapshots: hsl-to-rgb-for-reals@1.1.1: {} - html-tags@3.3.1: {} + html-escaper@2.0.2: {} htmlescape@1.1.1: {} @@ -15753,41 +14769,23 @@ snapshots: http-cache-semantics@4.1.1: {} - http-errors@2.0.0: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.1 - toidentifier: 1.0.1 - http-parser-js@0.5.8: {} http-proxy-agent@5.0.0: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color http-proxy-agent@7.0.0: dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color - http-proxy@1.18.1: - dependencies: - eventemitter3: 4.0.7 - follow-redirects: 1.15.2 - requires-port: 1.0.0 - transitivePeerDependencies: - - debug - - http-shutdown@1.2.2: {} - http-signature@1.2.0: dependencies: assert-plus: 1.0.0 @@ -15804,14 +14802,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -15823,8 +14821,6 @@ snapshots: human-signals@3.0.1: {} - human-signals@4.3.1: {} - humanize-ms@1.2.1: dependencies: ms: 2.1.3 @@ -15920,24 +14916,6 @@ snapshots: strip-ansi: 5.2.0 through: 2.3.8 - inquirer@9.2.0: - dependencies: - ansi-escapes: 6.2.0 - chalk: 5.2.0 - cli-cursor: 4.0.0 - cli-width: 4.0.0 - external-editor: 3.1.0 - figures: 5.0.0 - lodash: 4.17.21 - mute-stream: 1.0.0 - ora: 6.3.0 - run-async: 2.4.1 - rxjs: 7.8.0 - string-width: 5.1.2 - strip-ansi: 7.0.1 - through: 2.3.8 - wrap-ansi: 8.1.0 - insert-module-globals@7.2.1: dependencies: JSONStream: 1.3.5 @@ -15974,32 +14952,12 @@ snapshots: internmap@1.0.1: {} - ioredis@5.3.2: - dependencies: - '@ioredis/commands': 1.2.0 - cluster-key-slot: 1.1.2 - debug: 4.3.4 - denque: 2.1.0 - lodash.defaults: 4.2.0 - lodash.isarguments: 3.1.0 - redis-errors: 1.2.0 - redis-parser: 3.0.0 - standard-as-callback: 2.1.0 - transitivePeerDependencies: - - supports-color - iota-array@1.0.0: {} - ip-regex@5.0.0: {} - ip@1.1.8: {} ip@2.0.0: {} - ipaddr.js@1.9.1: {} - - iron-webcrypto@0.6.0: {} - is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -16043,10 +15001,6 @@ snapshots: is-buffer@2.0.5: {} - is-builtin-module@3.2.1: - dependencies: - builtin-modules: 3.3.0 - is-callable@1.2.7: {} is-ci@2.0.0: @@ -16055,7 +15009,7 @@ snapshots: is-ci@3.0.1: dependencies: - ci-info: 3.3.2 + ci-info: 3.8.0 is-core-module@2.9.0: dependencies: @@ -16069,8 +15023,6 @@ snapshots: is-docker@2.2.1: {} - is-docker@3.0.0: {} - is-empty@1.2.0: {} is-extglob@2.1.1: {} @@ -16102,16 +15054,14 @@ snapshots: is-interactive@1.0.0: {} - is-interactive@2.0.0: {} - is-lambda@1.0.1: {} is-map@2.0.2: {} - is-module@1.0.0: {} - is-negative-zero@2.0.2: {} + is-node-process@1.2.0: {} + is-npm@5.0.0: {} is-npm@6.0.0: {} @@ -16132,16 +15082,8 @@ snapshots: is-plain-obj@4.1.0: {} - is-primitive@3.0.1: {} - - is-promise@4.0.0: {} - is-property@1.0.2: {} - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.0 - is-regex@1.1.4: dependencies: call-bind: 1.0.2 @@ -16153,10 +15095,6 @@ snapshots: dependencies: call-bind: 1.0.2 - is-ssh@1.4.0: - dependencies: - protocols: 2.0.1 - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -16191,8 +15129,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-unicode-supported@1.3.0: {} - is-url@1.2.4: {} is-weakmap@2.0.1: {} @@ -16228,39 +15164,38 @@ snapshots: dependencies: ws: 8.13.0 + isomorphic-ws@5.0.0(ws@8.16.0): + dependencies: + ws: 8.16.0 + isstream@0.1.2: {} - jackspeak@2.3.1: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 + istanbul-lib-coverage@3.2.2: {} - jake@10.8.7: + istanbul-lib-report@3.0.1: dependencies: - async: 3.2.4 - chalk: 4.1.2 - filelist: 1.0.4 - minimatch: 3.1.2 + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 - jasmine-browser-runner@2.2.0(jasmine-core@5.1.1): + istanbul-lib-source-maps@5.0.6: dependencies: - ejs: 3.1.9 - express: 4.18.2 - glob: 10.3.4 - jasmine-core: 5.1.1 - selenium-webdriver: 4.12.0 + '@jridgewell/trace-mapping': 0.3.25 + debug: 4.3.7 + istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - - bufferutil - supports-color - - utf-8-validate - jasmine-core@5.1.1: {} + istanbul-reports@3.1.7: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 - jasmine@5.1.0: + jackspeak@3.4.3: dependencies: - glob: 10.3.4 - jasmine-core: 5.1.1 + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 jest-worker@27.5.1: dependencies: @@ -16268,8 +15203,6 @@ snapshots: merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@1.18.2: {} - jju@1.4.0: {} joycon@3.1.1: {} @@ -16323,8 +15256,6 @@ snapshots: json5@2.2.3: {} - jsonc-parser@3.2.0: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -16380,14 +15311,8 @@ snapshots: kind-of@6.0.3: {} - kleur@3.0.3: {} - kleur@4.1.5: {} - klona@2.0.6: {} - - knitwork@1.0.0: {} - labeled-stream-splicer@2.0.2: dependencies: inherits: 2.0.4 @@ -16407,10 +15332,6 @@ snapshots: dependencies: package-json: 8.1.0 - lazystream@1.0.1: - dependencies: - readable-stream: 2.3.8 - leven@2.1.0: {} levenshtein-edit-distance@1.0.0: {} @@ -16439,6 +15360,8 @@ snapshots: lilconfig@2.1.0: {} + lilconfig@3.1.2: {} + lines-and-columns@1.2.4: {} lines-and-columns@2.0.3: {} @@ -16462,17 +15385,6 @@ snapshots: - enquirer - supports-color - listhen@1.0.4: - dependencies: - clipboardy: 3.0.0 - colorette: 2.0.19 - defu: 6.1.2 - get-port-please: 3.0.1 - http-shutdown: 1.2.2 - ip-regex: 5.0.0 - node-forge: 1.3.1 - ufo: 1.1.1 - listr2@4.0.5(enquirer@2.3.6): dependencies: cli-truncate: 2.1.0 @@ -16514,17 +15426,8 @@ snapshots: load-tsconfig@0.2.5: {} - load-yaml-file@0.2.0: - dependencies: - graceful-fs: 4.2.11 - js-yaml: 3.14.1 - pify: 4.0.1 - strip-bom: 3.0.0 - loader-runner@4.3.0: {} - local-pkg@0.4.3: {} - locate-path@3.0.0: dependencies: p-locate: 3.0.0 @@ -16538,8 +15441,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash._reinterpolate@3.0.0: {} - lodash.camelcase@4.3.0: {} lodash.chunk@4.2.0: {} @@ -16548,16 +15449,10 @@ snapshots: lodash.debounce@4.0.8: {} - lodash.defaults@4.2.0: {} - - lodash.difference@4.5.0: {} - lodash.flatten@4.4.0: {} lodash.get@4.4.2: {} - lodash.isarguments@3.1.0: {} - lodash.isplainobject@4.0.6: {} lodash.kebabcase@4.1.1: {} @@ -16572,27 +15467,14 @@ snapshots: lodash.mergewith@4.6.2: {} - lodash.pick@4.4.0: {} - lodash.snakecase@4.1.1: {} lodash.sortby@4.7.0: {} lodash.startcase@4.4.0: {} - lodash.template@4.5.0: - dependencies: - lodash._reinterpolate: 3.0.0 - lodash.templatesettings: 4.2.0 - - lodash.templatesettings@4.2.0: - dependencies: - lodash._reinterpolate: 3.0.0 - lodash.truncate@4.4.2: {} - lodash.union@4.6.0: {} - lodash.uniq@4.5.0: {} lodash.upperfirst@4.3.1: {} @@ -16604,11 +15486,6 @@ snapshots: chalk: 4.1.2 is-unicode-supported: 0.1.0 - log-symbols@5.1.0: - dependencies: - chalk: 5.2.0 - is-unicode-supported: 1.3.0 - log-update@4.0.0: dependencies: ansi-escapes: 4.3.2 @@ -16626,6 +15503,8 @@ snapshots: lottie-web@5.11.0: {} + loupe@3.1.2: {} + lower-case@1.1.4: {} lowercase-keys@1.0.1: {} @@ -16634,7 +15513,7 @@ snapshots: lowercase-keys@3.0.0: {} - lru-cache@10.0.1: {} + lru-cache@10.4.3: {} lru-cache@4.1.5: dependencies: @@ -16651,8 +15530,6 @@ snapshots: lru-cache@7.18.3: {} - lru-cache@9.1.1: {} - lz-string@1.5.0: {} macos-release@2.5.1: {} @@ -16665,22 +15542,24 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 - magic-string@0.25.9: + magic-string@0.30.12: dependencies: - sourcemap-codec: 1.4.8 - - magic-string@0.27.0: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/sourcemap-codec': 1.5.0 - magic-string@0.30.0: + magicast@0.3.5: dependencies: - '@jridgewell/sourcemap-codec': 1.4.14 + '@babel/parser': 7.25.9 + '@babel/types': 7.25.9 + source-map-js: 1.2.1 make-dir@3.1.0: dependencies: semver: 6.3.1 + make-dir@4.0.0: + dependencies: + semver: 7.5.4 + make-error@1.3.6: {} make-fetch-happen@10.2.1: @@ -16930,41 +15809,16 @@ snapshots: mdast-util-toc@6.1.1: dependencies: '@types/extend': 3.0.1 - '@types/mdast': 3.0.11 - extend: 3.0.2 - github-slugger: 2.0.0 - mdast-util-to-string: 3.1.1 - unist-util-is: 5.2.1 - unist-util-visit: 4.1.2 - - mdast@3.0.0: {} - - mdn-data@2.0.14: {} - - mdn-data@2.0.28: {} - - mdn-data@2.0.30: {} - - media-typer@0.3.0: {} - - memory-fs@0.5.0: - dependencies: - errno: 0.1.8 - readable-stream: 2.3.8 - - meow@6.1.1: - dependencies: - '@types/minimist': 1.2.2 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.0 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 2.5.0 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.13.1 - yargs-parser: 18.1.3 + '@types/mdast': 3.0.11 + extend: 3.0.2 + github-slugger: 2.0.0 + mdast-util-to-string: 3.1.1 + unist-util-is: 5.2.1 + unist-util-visit: 4.1.2 + + mdast@3.0.0: {} + + mdn-data@2.0.14: {} meow@8.1.2: dependencies: @@ -16980,8 +15834,6 @@ snapshots: type-fest: 0.18.1 yargs-parser: 20.2.9 - merge-descriptors@1.0.1: {} - merge-source-map@1.0.4: dependencies: source-map: 0.5.7 @@ -17006,8 +15858,6 @@ snapshots: optionalDependencies: '@types/node': 18.0.6 - methods@1.1.2: {} - micromark-core-commonmark@1.0.6: dependencies: decode-named-character-reference: 1.0.2 @@ -17379,7 +16229,7 @@ snapshots: micromark@3.1.0: dependencies: '@types/debug': 4.1.7 - debug: 4.3.4 + debug: 4.3.7 decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.0.6 micromark-factory-space: 1.0.0 @@ -17401,7 +16251,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.4 + debug: 4.3.7 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -17436,12 +16286,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mime@1.6.0: {} - - mime@2.5.2: {} - - mime@3.0.0: {} - mimic-fn@1.2.0: {} mimic-fn@2.1.0: {} @@ -17491,7 +16335,7 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimatch@9.0.3: + minimatch@9.0.5: dependencies: brace-expansion: 2.0.1 @@ -17540,7 +16384,7 @@ snapshots: minipass@4.2.4: {} - minipass@7.0.3: {} + minipass@7.1.2: {} minizlib@2.1.2: dependencies: @@ -17549,8 +16393,6 @@ snapshots: mitt@3.0.1: {} - mixme@0.5.5: {} - mkdirp-classic@0.5.3: {} mkdirp@1.0.4: {} @@ -17593,13 +16435,6 @@ snapshots: ml-xsadd@2.0.0: {} - mlly@1.2.0: - dependencies: - acorn: 8.8.2 - pathe: 1.1.0 - pkg-types: 1.0.2 - ufo: 1.1.1 - module-deps@6.2.3: dependencies: JSONStream: 1.3.5 @@ -17624,12 +16459,38 @@ snapshots: mri@1.2.0: {} + mrmime@2.0.0: {} + ms@2.0.0: {} ms@2.1.2: {} ms@2.1.3: {} + msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4): + dependencies: + '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.0.0(@types/node@22.8.7) + '@mswjs/interceptors': 0.36.5 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + chalk: 4.1.2 + graphql: 16.8.1 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + strict-event-emitter: 0.5.1 + type-fest: 4.26.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 4.7.4 + transitivePeerDependencies: + - '@types/node' + muggle-string@0.4.1: {} multistream@2.1.1: @@ -17639,7 +16500,7 @@ snapshots: mute-stream@0.0.7: {} - mute-stream@1.0.0: {} + mute-stream@2.0.0: {} mutexify@1.4.0: dependencies: @@ -17678,11 +16539,11 @@ snapshots: nanoid@3.3.6: {} - nanoid@4.0.2: {} + nanoid@3.3.7: {} nanospinner@1.1.0: dependencies: - picocolors: 1.0.0 + picocolors: 1.1.1 natural-compare-lite@1.4.0: {} @@ -17761,86 +16622,12 @@ snapshots: - '@babel/core' - babel-plugin-macros - nitropack@2.3.3(encoding@0.1.13): - dependencies: - '@cloudflare/kv-asset-handler': 0.3.0 - '@netlify/functions': 1.4.0 - '@rollup/plugin-alias': 5.0.0(rollup@3.21.0) - '@rollup/plugin-commonjs': 24.1.0(rollup@3.21.0) - '@rollup/plugin-inject': 5.0.3(rollup@3.21.0) - '@rollup/plugin-json': 6.0.0(rollup@3.21.0) - '@rollup/plugin-node-resolve': 15.0.2(rollup@3.21.0) - '@rollup/plugin-replace': 5.0.2(rollup@3.21.0) - '@rollup/plugin-terser': 0.4.1(rollup@3.21.0) - '@rollup/plugin-wasm': 6.1.2(rollup@3.21.0) - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - '@vercel/nft': 0.22.6(encoding@0.1.13) - archiver: 5.3.1 - c12: 1.4.1 - chalk: 5.2.0 - chokidar: 3.5.3 - consola: 3.1.0 - cookie-es: 0.5.0 - defu: 6.1.2 - destr: 1.2.2 - dot-prop: 7.2.0 - esbuild: 0.17.18 - escape-string-regexp: 5.0.0 - etag: 1.8.1 - fs-extra: 11.1.1 - globby: 13.1.4 - gzip-size: 7.0.0 - h3: 1.6.4 - hookable: 5.5.3 - http-proxy: 1.18.1 - is-primitive: 3.0.1 - jiti: 1.18.2 - klona: 2.0.6 - knitwork: 1.0.0 - listhen: 1.0.4 - mime: 3.0.0 - mlly: 1.2.0 - mri: 1.2.0 - node-fetch-native: 1.1.0 - ofetch: 1.0.1 - ohash: 1.1.2 - pathe: 1.1.0 - perfect-debounce: 0.1.3 - pkg-types: 1.0.2 - pretty-bytes: 6.1.0 - radix3: 1.0.1 - rollup: 3.21.0 - rollup-plugin-visualizer: 5.9.0(rollup@3.21.0) - scule: 1.0.0 - semver: 7.5.0 - serve-placeholder: 2.0.1 - serve-static: 1.15.0 - source-map-support: 0.5.21 - std-env: 3.3.2 - ufo: 1.1.1 - unenv: 1.4.1 - unimport: 3.0.6(rollup@3.21.0) - unstorage: 1.5.0 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@planetscale/database' - - debug - - encoding - - supports-color - no-case@2.3.2: dependencies: lower-case: 1.1.4 node-domexception@1.0.0: {} - node-fetch-native@1.1.0: {} - node-fetch@2.6.7(encoding@0.1.13): dependencies: whatwg-url: 5.0.0 @@ -17865,10 +16652,6 @@ snapshots: fetch-blob: 3.2.0 formdata-polyfill: 4.0.10 - node-forge@1.3.1: {} - - node-gyp-build@4.6.0: {} - node-gyp@9.3.1: dependencies: env-paths: 2.2.1 @@ -17904,10 +16687,6 @@ snapshots: dependencies: abbrev: 1.1.1 - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 - nopt@6.0.0: dependencies: abbrev: 1.1.1 @@ -17945,8 +16724,6 @@ snapshots: normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - normalize-url@4.5.1: {} normalize-url@8.0.0: {} @@ -18044,13 +16821,6 @@ snapshots: dependencies: path-key: 4.0.0 - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - npmlog@6.0.2: dependencies: are-we-there-yet: 3.0.1 @@ -18064,89 +16834,6 @@ snapshots: number-is-nan@1.0.1: {} - nuxi@3.4.2: - optionalDependencies: - fsevents: 2.3.3 - - nuxt@3.4.2(@types/node@18.0.6)(encoding@0.1.13)(eslint@8.39.0)(optionator@0.9.1)(rollup@3.21.0)(terser@5.17.1)(typescript@5.6.3)(vue-tsc@2.1.10(typescript@5.6.3)): - dependencies: - '@nuxt/devalue': 2.0.0 - '@nuxt/kit': 3.4.2(rollup@3.21.0) - '@nuxt/schema': 3.4.2(rollup@3.21.0) - '@nuxt/telemetry': 2.2.0(rollup@3.21.0) - '@nuxt/ui-templates': 1.1.1 - '@nuxt/vite-builder': 3.4.2(@types/node@18.0.6)(eslint@8.39.0)(optionator@0.9.1)(rollup@3.21.0)(terser@5.17.1)(typescript@5.6.3)(vue-tsc@2.1.10(typescript@5.6.3))(vue@3.2.47) - '@types/node': 18.0.6 - '@unhead/ssr': 1.1.26 - '@unhead/vue': 1.1.26(vue@3.2.47) - '@vue/shared': 3.2.47 - chokidar: 3.5.3 - cookie-es: 0.5.0 - defu: 6.1.2 - destr: 1.2.2 - devalue: 4.3.0 - escape-string-regexp: 5.0.0 - estree-walker: 3.0.3 - fs-extra: 11.1.1 - globby: 13.1.4 - h3: 1.6.4 - hookable: 5.5.3 - jiti: 1.18.2 - klona: 2.0.6 - knitwork: 1.0.0 - local-pkg: 0.4.3 - magic-string: 0.30.0 - mlly: 1.2.0 - nitropack: 2.3.3(encoding@0.1.13) - nuxi: 3.4.2 - nypm: 0.2.0 - ofetch: 1.0.1 - ohash: 1.1.2 - pathe: 1.1.0 - perfect-debounce: 0.1.3 - prompts: 2.4.2 - scule: 1.0.0 - strip-literal: 1.0.1 - ufo: 1.1.1 - unctx: 2.3.0 - unenv: 1.4.1 - unimport: 3.0.6(rollup@3.21.0) - unplugin: 1.3.1 - untyped: 1.3.2 - vue: 3.2.47 - vue-bundle-renderer: 1.0.3 - vue-devtools-stub: 0.1.0 - vue-router: 4.1.6(vue@3.2.47) - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@planetscale/database' - - debug - - encoding - - eslint - - less - - meow - - optionator - - rollup - - sass - - stylelint - - stylus - - sugarss - - supports-color - - terser - - typescript - - vls - - vti - - vue-tsc - - nypm@0.2.0: - dependencies: - execa: 7.1.1 - oauth-sign@0.9.0: {} object-assign@4.1.1: {} @@ -18192,18 +16879,6 @@ snapshots: define-properties: 1.2.0 es-abstract: 1.21.2 - ofetch@1.0.1: - dependencies: - destr: 1.2.2 - node-fetch-native: 1.1.0 - ufo: 1.1.1 - - ohash@1.1.2: {} - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - on-net-listen@1.1.2: {} once@1.4.0: @@ -18227,12 +16902,6 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - open@8.4.2: - dependencies: - define-lazy-prop: 2.0.0 - is-docker: 2.2.1 - is-wsl: 2.2.0 - opn@5.5.0: dependencies: is-wsl: 1.1.0 @@ -18267,18 +16936,6 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 - ora@6.3.0: - dependencies: - chalk: 5.2.0 - cli-cursor: 4.0.0 - cli-spinners: 2.7.0 - is-interactive: 2.0.0 - is-unicode-supported: 1.3.0 - log-symbols: 5.1.0 - stdin-discarder: 0.1.0 - strip-ansi: 7.0.1 - wcwidth: 1.0.1 - os-browserify@0.3.0: {} os-name@4.0.1: @@ -18290,6 +16947,8 @@ snapshots: outdent@0.5.0: {} + outvariant@1.4.3: {} + p-cancelable@1.1.0: {} p-cancelable@3.0.0: {} @@ -18330,7 +16989,7 @@ snapshots: dependencies: '@tootallnate/quickjs-emscripten': 0.23.0 agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.7 get-uri: 6.0.2 http-proxy-agent: 7.0.0 https-proxy-agent: 7.0.2 @@ -18345,6 +17004,8 @@ snapshots: ip: 1.1.8 netmask: 2.0.2 + package-json-from-dist@1.0.1: {} + package-json@6.5.0: dependencies: got: 9.6.0 @@ -18359,6 +17020,8 @@ snapshots: registry-url: 6.0.1 semver: 7.5.4 + package-manager-detector@0.2.2: {} + pacote@13.6.2: dependencies: '@npmcli/git': 3.0.2 @@ -18421,11 +17084,6 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - parse-git-config@3.0.0: - dependencies: - git-config-path: 2.0.0 - ini: 1.3.8 - parse-github-url@1.0.2: {} parse-json@5.2.0: @@ -18442,18 +17100,8 @@ snapshots: json-parse-even-better-errors: 2.3.1 lines-and-columns: 2.0.3 - parse-path@7.0.0: - dependencies: - protocols: 2.0.1 - - parse-url@8.1.0: - dependencies: - parse-path: 7.0.0 - parse5@6.0.1: {} - parseurl@1.3.3: {} - path-browserify@1.0.1: {} path-exists@3.0.0: {} @@ -18470,16 +17118,18 @@ snapshots: path-platform@0.11.15: {} - path-scurry@1.10.1: + path-scurry@1.11.1: dependencies: - lru-cache: 10.0.1 - minipass: 7.0.3 + lru-cache: 10.4.3 + minipass: 7.1.2 - path-to-regexp@0.1.7: {} + path-to-regexp@6.3.0: {} path-type@4.0.0: {} - pathe@1.1.0: {} + pathe@1.1.2: {} + + pathval@2.0.0: {} pause-stream@0.0.11: dependencies: @@ -18497,14 +17147,16 @@ snapshots: pend@1.2.0: {} - perfect-debounce@0.1.3: {} - performance-now@2.1.0: {} picocolors@1.0.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.2: {} + pidtree@0.6.0: {} pify@2.3.0: {} @@ -18517,230 +17169,59 @@ snapshots: dependencies: find-up: 4.1.0 - pkg-types@1.0.2: - dependencies: - jsonc-parser: 3.2.0 - mlly: 1.2.0 - pathe: 1.1.0 - pkg-up@3.1.0: dependencies: find-up: 3.0.0 - pluralize@8.0.0: {} - - postcss-calc@8.2.4(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-selector-parser: 6.0.11 - postcss-value-parser: 4.2.0 - - postcss-colormin@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-convert-values@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - - postcss-discard-duplicates@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - - postcss-discard-empty@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 + playwright-core@1.48.2: {} - postcss-discard-overridden@6.0.0(postcss@8.4.23): + playwright@1.48.2: dependencies: - postcss: 8.4.23 - - postcss-import-resolver@2.0.0: - dependencies: - enhanced-resolve: 4.5.0 + playwright-core: 1.48.2 + optionalDependencies: + fsevents: 2.3.2 - postcss-import@13.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - read-cache: 1.0.0 - resolve: 1.22.1 + pluralize@8.0.0: {} - postcss-import@15.1.0(postcss@8.4.23): + postcss-import@13.0.0(postcss@8.4.47): dependencies: - postcss: 8.4.23 + postcss: 8.4.47 postcss-value-parser: 4.2.0 read-cache: 1.0.0 resolve: 1.22.1 - postcss-load-config@3.1.4(postcss@8.4.23)(ts-node@10.9.1(@types/node@18.0.6)(typescript@4.7.4)): + postcss-load-config@6.0.1(postcss@8.4.47)(yaml@2.6.0): dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 + lilconfig: 3.1.2 optionalDependencies: - postcss: 8.4.23 - ts-node: 10.9.1(@types/node@18.0.6)(typescript@4.7.4) - - postcss-merge-longhand@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - stylehacks: 6.0.0(postcss@8.4.23) - - postcss-merge-rules@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - caniuse-api: 3.0.0 - cssnano-utils: 4.0.0(postcss@8.4.23) - postcss: 8.4.23 - postcss-selector-parser: 6.0.11 - - postcss-minify-font-values@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@6.0.0(postcss@8.4.23): - dependencies: - colord: 2.9.3 - cssnano-utils: 4.0.0(postcss@8.4.23) - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-minify-params@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - cssnano-utils: 4.0.0(postcss@8.4.23) - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-selector-parser: 6.0.11 - - postcss-normalize-charset@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - - postcss-normalize-display-values@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@6.0.0(postcss@8.4.23): - dependencies: - cssnano-utils: 4.0.0(postcss@8.4.23) - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - caniuse-api: 3.0.0 - postcss: 8.4.23 - - postcss-reduce-transforms@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - - postcss-selector-parser@6.0.11: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-value-parser: 4.2.0 - svgo: 3.0.2 - - postcss-unique-selectors@6.0.0(postcss@8.4.23): - dependencies: - postcss: 8.4.23 - postcss-selector-parser: 6.0.11 - - postcss-url@10.1.3(postcss@8.4.23): - dependencies: - make-dir: 3.1.0 - mime: 2.5.2 - minimatch: 3.0.4 - postcss: 8.4.23 - xxhashjs: 0.2.2 + postcss: 8.4.47 + yaml: 2.6.0 postcss-value-parser@4.2.0: {} postcss@8.4.14: dependencies: nanoid: 3.3.6 - picocolors: 1.0.0 + picocolors: 1.1.1 source-map-js: 1.0.2 postcss@8.4.21: dependencies: nanoid: 3.3.6 - picocolors: 1.0.0 + picocolors: 1.1.1 source-map-js: 1.0.2 postcss@8.4.23: dependencies: nanoid: 3.3.6 - picocolors: 1.0.0 - source-map-js: 1.0.2 + picocolors: 1.1.1 + source-map-js: 1.2.1 - preferred-pm@3.0.3: + postcss@8.4.47: dependencies: - find-up: 5.0.0 - find-yarn-workspace-root2: 1.2.16 - path-exists: 4.0.0 - which-pm: 2.0.0 + nanoid: 3.3.7 + picocolors: 1.1.1 + source-map-js: 1.2.1 prelude-ls@1.1.2: {} @@ -18766,14 +17247,10 @@ snapshots: mvdan-sh: 0.5.0 prettier: 2.7.1 - prettier@1.19.1: {} - prettier@2.7.1: {} pretty-bytes@5.6.0: {} - pretty-bytes@6.1.0: {} - pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 @@ -18804,11 +17281,6 @@ snapshots: kleur: 4.1.5 sisteransi: 1.0.5 - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -18839,20 +17311,13 @@ snapshots: generate-object-property: 1.2.0 protocol-buffers-encodings: 1.2.0 protocol-buffers-schema: 3.6.0 - signed-varint: 2.0.1 - varint: 5.0.2 - - protocols@2.0.1: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 + signed-varint: 2.0.1 + varint: 5.0.2 proxy-agent@6.3.1: dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.7 http-proxy-agent: 7.0.0 https-proxy-agent: 7.0.2 lru-cache: 7.18.3 @@ -18864,8 +17329,6 @@ snapshots: proxy-from-env@1.1.0: {} - prr@1.0.1: {} - ps-tree@1.2.0: dependencies: event-stream: 3.3.4 @@ -18966,10 +17429,6 @@ snapshots: q@1.5.1: {} - qs@6.11.0: - dependencies: - side-channel: 1.0.4 - qs@6.5.3: {} querystring-es3@0.2.1: {} @@ -18992,8 +17451,6 @@ snapshots: minimist: 1.2.8 through2: 2.0.5 - radix3@1.0.1: {} - randombytes@2.1.0: dependencies: safe-buffer: 5.2.1 @@ -19003,15 +17460,6 @@ snapshots: randombytes: 2.1.0 safe-buffer: 5.2.1 - range-parser@1.2.1: {} - - raw-body@2.5.1: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - rc-config-loader@4.1.2: dependencies: debug: 4.3.4 @@ -19021,12 +17469,6 @@ snapshots: transitivePeerDependencies: - supports-color - rc9@2.1.0: - dependencies: - defu: 6.1.2 - destr: 1.2.2 - flat: 5.0.2 - rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -19095,7 +17537,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 js-yaml: 3.14.1 pify: 4.0.1 strip-bom: 3.0.0 @@ -19121,10 +17563,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readdir-glob@1.1.3: - dependencies: - minimatch: 5.1.6 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 @@ -19134,12 +17572,6 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 - redis-errors@1.2.0: {} - - redis-parser@3.0.0: - dependencies: - redis-errors: 1.2.0 - regenerator-runtime@0.13.11: {} regexp-tree@0.1.24: {} @@ -19715,11 +18147,6 @@ snapshots: onetime: 5.1.2 signal-exit: 3.0.7 - restore-cursor@4.0.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - retimer@3.0.0: {} retry@0.12.0: {} @@ -19739,21 +18166,30 @@ snapshots: robust-predicates@3.0.2: {} - rollup-plugin-visualizer@5.9.0(rollup@3.21.0): - dependencies: - open: 8.4.2 - picomatch: 2.3.1 - source-map: 0.7.4 - yargs: 17.5.1 - optionalDependencies: - rollup: 3.21.0 - - rollup@2.79.1: + rollup@3.21.0: optionalDependencies: fsevents: 2.3.3 - rollup@3.21.0: + rollup@4.24.0: + dependencies: + '@types/estree': 1.0.6 optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.24.0 + '@rollup/rollup-android-arm64': 4.24.0 + '@rollup/rollup-darwin-arm64': 4.24.0 + '@rollup/rollup-darwin-x64': 4.24.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.24.0 + '@rollup/rollup-linux-arm-musleabihf': 4.24.0 + '@rollup/rollup-linux-arm64-gnu': 4.24.0 + '@rollup/rollup-linux-arm64-musl': 4.24.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.24.0 + '@rollup/rollup-linux-riscv64-gnu': 4.24.0 + '@rollup/rollup-linux-s390x-gnu': 4.24.0 + '@rollup/rollup-linux-x64-gnu': 4.24.0 + '@rollup/rollup-linux-x64-musl': 4.24.0 + '@rollup/rollup-win32-arm64-msvc': 4.24.0 + '@rollup/rollup-win32-ia32-msvc': 4.24.0 + '@rollup/rollup-win32-x64-msvc': 4.24.0 fsevents: 2.3.3 run-async@2.4.1: {} @@ -19808,19 +18244,8 @@ snapshots: estree-is-function: 1.0.0 get-assigned-identifiers: 1.2.0 - scule@1.0.0: {} - seedrandom@3.0.5: {} - selenium-webdriver@4.12.0: - dependencies: - jszip: 3.10.1 - tmp: 0.2.1 - ws: 8.13.0 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - semver-diff@3.1.1: dependencies: semver: 6.3.1 @@ -19831,8 +18256,6 @@ snapshots: semver-utils@1.1.4: {} - semver@5.7.1: {} - semver@5.7.2: {} semver@6.3.0: {} @@ -19865,47 +18288,14 @@ snapshots: dependencies: lru-cache: 6.0.0 - send@0.18.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - serialize-javascript@6.0.1: dependencies: randombytes: 2.1.0 - serve-placeholder@2.0.1: - dependencies: - defu: 6.1.2 - - serve-static@1.15.0: - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - transitivePeerDependencies: - - supports-color - set-blocking@2.0.0: {} setimmediate@1.0.5: {} - setprototypeof@1.2.0: {} - sha.js@2.4.11: dependencies: inherits: 2.0.4 @@ -19971,6 +18361,8 @@ snapshots: get-intrinsic: 1.2.0 object-inspect: 1.12.3 + siginfo@2.0.0: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -19995,6 +18387,12 @@ snapshots: sinusoidal-decimal@1.0.0: {} + sirv@2.0.4: + dependencies: + '@polka/url': 1.0.0-next.28 + mrmime: 2.0.0 + totalist: 3.0.1 + sisteransi@1.0.5: {} size-limit@8.2.6: @@ -20029,21 +18427,10 @@ snapshots: smart-buffer@4.2.0: {} - smartwrap@2.0.2: - dependencies: - array.prototype.flat: 1.3.1 - breakword: 1.0.5 - grapheme-splitter: 1.0.4 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 15.4.1 - - smob@0.0.6: {} - socks-proxy-agent@7.0.0: dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -20051,7 +18438,7 @@ snapshots: socks-proxy-agent@8.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4 + debug: 4.3.7 socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -20081,8 +18468,6 @@ snapshots: source-map@0.6.1: {} - source-map@0.7.4: {} - source-map@0.8.0-beta.0: dependencies: whatwg-url: 7.1.0 @@ -20148,6 +18533,8 @@ snapshots: dependencies: stackframe: 1.3.4 + stackback@0.0.2: {} + stackframe@1.3.4: {} stacktrace-gps@3.1.2: @@ -20161,8 +18548,6 @@ snapshots: stack-generator: 2.0.10 stacktrace-gps: 3.1.2 - standard-as-callback@2.1.0: {} - static-eval@2.1.0: dependencies: escodegen: 1.14.3 @@ -20171,7 +18556,7 @@ snapshots: dependencies: acorn-node: 1.8.2 concat-stream: 1.6.2 - convert-source-map: 1.8.0 + convert-source-map: 1.9.0 duplexer2: 0.1.4 escodegen: 1.14.3 has: 1.0.3 @@ -20186,11 +18571,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.3.2: {} - - stdin-discarder@0.1.0: - dependencies: - bl: 5.1.0 + std-env@3.7.0: {} stop-iteration-iterator@1.0.0: dependencies: @@ -20233,10 +18614,6 @@ snapshots: end-of-stream: 1.4.4 readable-stream: 2.3.8 - stream-transform@2.1.3: - dependencies: - mixme: 0.5.5 - streaming-json-stringify@3.1.0: dependencies: json-stringify-safe: 5.0.1 @@ -20249,6 +18626,8 @@ snapshots: fast-fifo: 1.3.2 queue-tick: 1.0.1 + strict-event-emitter@0.5.1: {} + string-argv@0.3.1: {} string-env-interpolation@1.0.1: {} @@ -20344,10 +18723,6 @@ snapshots: dependencies: ansi-regex: 6.0.1 - strip-ansi@7.1.0: - dependencies: - ansi-regex: 6.0.1 - strip-bom@3.0.0: {} strip-bom@4.0.0: {} @@ -20368,10 +18743,6 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@1.0.1: - dependencies: - acorn: 8.8.2 - strtok3@9.0.1: dependencies: '@tokenizer/token': 0.3.0 @@ -20382,23 +18753,17 @@ snapshots: client-only: 0.0.1 react: 18.2.0 - stylehacks@6.0.0(postcss@8.4.23): - dependencies: - browserslist: 4.21.5 - postcss: 8.4.23 - postcss-selector-parser: 6.0.11 - stylis@4.2.0: {} subarg@1.0.0: dependencies: minimist: 1.2.8 - sucrase@3.34.0: + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.3 commander: 4.1.1 - glob: 7.1.6 + glob: 10.4.5 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.6 @@ -20424,8 +18789,6 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svg-tags@1.0.0: {} - svgo@2.8.0: dependencies: '@trysound/sax': 0.2.0 @@ -20433,18 +18796,9 @@ snapshots: css-select: 4.3.0 css-tree: 1.1.3 csso: 4.2.0 - picocolors: 1.0.0 + picocolors: 1.1.1 stable: 0.1.8 - svgo@3.0.2: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 5.1.0 - css-tree: 2.3.1 - csso: 5.0.5 - picocolors: 1.0.0 - syncpack@8.2.4: dependencies: chalk: 4.1.2 @@ -20472,8 +18826,6 @@ snapshots: tachyons@4.12.0: {} - tapable@1.1.3: {} - tapable@2.2.1: {} tar-fs@2.1.1: @@ -20516,7 +18868,7 @@ snapshots: terser-webpack-plugin@5.3.9(esbuild@0.14.49)(webpack@5.88.2(esbuild@0.14.49)): dependencies: - '@jridgewell/trace-mapping': 0.3.18 + '@jridgewell/trace-mapping': 0.3.19 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.1 @@ -20539,6 +18891,12 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 + test-exclude@7.0.1: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 10.4.5 + minimatch: 9.0.5 + text-extensions@1.9.0: {} text-table@0.2.0: {} @@ -20573,15 +18931,24 @@ snapshots: timestring@6.0.0: {} - tiny-invariant@1.3.1: {} + tinybench@2.9.0: {} - tmp@0.0.33: + tinyexec@0.3.1: {} + + tinyglobby@0.2.9: dependencies: - os-tmpdir: 1.0.2 + fdir: 6.4.2(picomatch@4.0.2) + picomatch: 4.0.2 + + tinypool@1.0.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} - tmp@0.2.1: + tmp@0.0.33: dependencies: - rimraf: 3.0.2 + os-tmpdir: 1.0.2 to-fast-properties@2.0.0: {} @@ -20607,13 +18974,13 @@ snapshots: toggle-selection@1.0.6: {} - toidentifier@1.0.1: {} - token-types@6.0.0: dependencies: '@tokenizer/token': 0.3.0 ieee754: 1.2.1 + totalist@3.0.1: {} + touch@3.1.0: dependencies: nopt: 1.0.10 @@ -20630,6 +18997,13 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@4.1.4: + dependencies: + psl: 1.9.0 + punycode: 2.3.0 + universalify: 0.2.0 + url-parse: 1.5.10 + tr46@0.0.3: {} tr46@1.0.1: @@ -20639,7 +19013,7 @@ snapshots: transform-ast@2.4.4: dependencies: acorn-node: 1.8.2 - convert-source-map: 1.8.0 + convert-source-map: 1.9.0 dash-ast: 1.0.0 is-buffer: 2.0.5 magic-string: 0.23.2 @@ -20699,28 +19073,32 @@ snapshots: tslib@2.6.2: {} - tsup@6.1.3(postcss@8.4.23)(ts-node@10.9.1(@types/node@18.0.6)(typescript@4.7.4))(typescript@4.7.4): + tsup@8.3.0(postcss@8.4.47)(typescript@4.7.4)(yaml@2.6.0): dependencies: - bundle-require: 3.1.2(esbuild@0.14.49) + bundle-require: 5.0.0(esbuild@0.23.1) cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4 - esbuild: 0.14.49 + chokidar: 3.6.0 + consola: 3.2.3 + debug: 4.3.7 + esbuild: 0.23.1 execa: 5.1.1 - globby: 11.1.0 joycon: 3.1.1 - postcss-load-config: 3.1.4(postcss@8.4.23)(ts-node@10.9.1(@types/node@18.0.6)(typescript@4.7.4)) + picocolors: 1.1.1 + postcss-load-config: 6.0.1(postcss@8.4.47)(yaml@2.6.0) resolve-from: 5.0.0 - rollup: 2.79.1 + rollup: 4.24.0 source-map: 0.8.0-beta.0 - sucrase: 3.34.0 + sucrase: 3.35.0 + tinyglobby: 0.2.9 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.4.23 + postcss: 8.4.47 typescript: 4.7.4 transitivePeerDependencies: + - jiti - supports-color - - ts-node + - tsx + - yaml tsutils@3.21.0(typescript@4.7.4): dependencies: @@ -20739,16 +19117,6 @@ snapshots: tty-browserify@0.0.1: {} - tty-table@4.1.6: - dependencies: - chalk: 4.1.2 - csv: 5.5.3 - kleur: 4.1.5 - smartwrap: 2.0.2 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - yargs: 17.5.1 - tunnel-agent@0.6.0: dependencies: safe-buffer: 5.2.1 @@ -20828,8 +19196,6 @@ snapshots: type-component@0.0.1: {} - type-fest@0.13.1: {} - type-fest@0.18.1: {} type-fest@0.20.2: {} @@ -20844,12 +19210,7 @@ snapshots: type-fest@2.19.0: {} - type-fest@3.9.0: {} - - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 + type-fest@4.26.1: {} type@1.2.0: {} @@ -20878,8 +19239,6 @@ snapshots: typescript@5.6.3: {} - ufo@1.1.1: {} - uint8array-extras@1.4.0: {} umd@3.0.3: {} @@ -20896,15 +19255,6 @@ snapshots: buffer: 5.7.1 through: 2.3.8 - uncrypto@0.1.2: {} - - unctx@2.3.0: - dependencies: - acorn: 8.8.2 - estree-walker: 3.0.3 - magic-string: 0.30.0 - unplugin: 1.3.1 - undeclared-identifiers@1.1.3: dependencies: acorn-node: 1.8.2 @@ -20915,19 +19265,7 @@ snapshots: undefsafe@2.0.5: {} - unenv@1.4.1: - dependencies: - defu: 6.1.2 - mime: 3.0.0 - node-fetch-native: 1.1.0 - pathe: 1.1.0 - - unhead@1.1.26: - dependencies: - '@unhead/dom': 1.1.26 - '@unhead/schema': 1.1.26 - '@unhead/shared': 1.1.26 - hookable: 5.5.3 + undici-types@6.19.8: {} unified-args@10.0.0: dependencies: @@ -20951,7 +19289,7 @@ snapshots: '@types/node': 18.0.6 '@types/unist': 2.0.6 concat-stream: 2.0.0 - debug: 4.3.4 + debug: 4.3.7 fault: 2.0.1 glob: 8.1.0 ignore: 5.2.4 @@ -20966,7 +19304,7 @@ snapshots: vfile-message: 3.1.4 vfile-reporter: 7.0.5 vfile-statistics: 2.0.1 - yaml: 2.2.1 + yaml: 2.6.0 transitivePeerDependencies: - supports-color @@ -20979,7 +19317,7 @@ snapshots: '@types/node': 17.0.45 '@types/unist': 2.0.6 concat-stream: 2.0.0 - debug: 4.3.4 + debug: 4.3.7 fault: 2.0.1 glob: 7.2.3 ignore: 5.2.4 @@ -21054,22 +19392,6 @@ snapshots: trough: 1.0.5 vfile: 4.2.1 - unimport@3.0.6(rollup@3.21.0): - dependencies: - '@rollup/pluginutils': 5.0.2(rollup@3.21.0) - escape-string-regexp: 5.0.0 - fast-glob: 3.2.12 - local-pkg: 0.4.3 - magic-string: 0.30.0 - mlly: 1.2.0 - pathe: 1.1.0 - pkg-types: 1.0.2 - scule: 1.0.0 - strip-literal: 1.0.1 - unplugin: 1.3.1 - transitivePeerDependencies: - - rollup - uniq@1.0.1: {} unique-filename@2.0.1: @@ -21187,48 +19509,11 @@ snapshots: dependencies: normalize-path: 2.1.1 - unpipe@1.0.0: {} - - unplugin@1.3.1: - dependencies: - acorn: 8.8.2 - chokidar: 3.5.3 - webpack-sources: 3.2.3 - webpack-virtual-modules: 0.5.0 - - unstorage@1.5.0: - dependencies: - anymatch: 3.1.3 - chokidar: 3.5.3 - destr: 1.2.2 - h3: 1.6.4 - ioredis: 5.3.2 - listhen: 1.0.4 - lru-cache: 9.1.1 - mri: 1.2.0 - node-fetch-native: 1.1.0 - ofetch: 1.0.1 - ufo: 1.1.1 - transitivePeerDependencies: - - supports-color - - untyped@1.3.2: - dependencies: - '@babel/core': 7.21.4 - '@babel/standalone': 7.21.4 - '@babel/types': 7.21.4 - defu: 6.1.2 - jiti: 1.18.2 - mri: 1.2.0 - scule: 1.0.0 - transitivePeerDependencies: - - supports-color - update-browserslist-db@1.0.11(browserslist@4.21.5): dependencies: browserslist: 4.21.5 escalade: 3.1.1 - picocolors: 1.0.0 + picocolors: 1.1.1 update-notifier@5.1.0: dependencies: @@ -21260,7 +19545,7 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.1.0 - semver: 7.5.0 + semver: 7.5.4 semver-diff: 4.0.0 xdg-basedir: 5.1.0 @@ -21304,8 +19589,6 @@ snapshots: is-typed-array: 1.1.10 which-typed-array: 1.1.9 - utils-merge@1.0.1: {} - uuid-parse@1.1.0: {} uuid@3.4.0: {} @@ -21342,8 +19625,6 @@ snapshots: varint@5.0.2: {} - vary@1.1.2: {} - verror@1.10.0: dependencies: assert-plus: 1.0.0 @@ -21425,96 +19706,84 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@0.30.1(@types/node@18.0.6)(terser@5.17.1): + vite-node@2.1.3(@types/node@22.8.7)(terser@5.17.1): dependencies: cac: 6.7.14 - debug: 4.3.4 - mlly: 1.2.0 - pathe: 1.1.0 - picocolors: 1.0.0 - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) + debug: 4.3.7 + pathe: 1.1.2 + vite: 5.4.10(@types/node@22.8.7)(terser@5.17.1) transitivePeerDependencies: - '@types/node' - less + - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color - terser - vite-plugin-checker@0.5.6(eslint@8.39.0)(optionator@0.9.1)(typescript@5.6.3)(vite@4.3.2(@types/node@18.0.6)(terser@5.17.1))(vue-tsc@2.1.10(typescript@5.6.3)): - dependencies: - '@babel/code-frame': 7.21.4 - ansi-escapes: 4.3.2 - chalk: 4.1.2 - chokidar: 3.5.3 - commander: 8.3.0 - fast-glob: 3.2.12 - fs-extra: 11.1.1 - lodash.debounce: 4.0.8 - lodash.pick: 4.4.0 - npm-run-path: 4.0.1 - strip-ansi: 6.0.1 - tiny-invariant: 1.3.1 - vite: 4.3.2(@types/node@18.0.6)(terser@5.17.1) - vscode-languageclient: 7.0.0 - vscode-languageserver: 7.0.0 - vscode-languageserver-textdocument: 1.0.8 - vscode-uri: 3.0.7 - optionalDependencies: - eslint: 8.39.0 - optionator: 0.9.1 - typescript: 5.6.3 - vue-tsc: 2.1.10(typescript@5.6.3) + vite-plugin-arraybuffer@0.0.8: {} - vite@4.3.2(@types/node@18.0.6)(terser@5.17.1): + vite@4.3.2(@types/node@22.8.7)(terser@5.17.1): dependencies: esbuild: 0.17.18 postcss: 8.4.21 rollup: 3.21.0 optionalDependencies: - '@types/node': 18.0.6 + '@types/node': 22.8.7 fsevents: 2.3.3 terser: 5.17.1 - vm-browserify@1.1.2: {} - - vscode-jsonrpc@6.0.0: {} - - vscode-languageclient@7.0.0: + vite@5.4.10(@types/node@22.8.7)(terser@5.17.1): dependencies: - minimatch: 3.1.2 - semver: 7.5.4 - vscode-languageserver-protocol: 3.16.0 - - vscode-languageserver-protocol@3.16.0: - dependencies: - vscode-jsonrpc: 6.0.0 - vscode-languageserver-types: 3.16.0 - - vscode-languageserver-textdocument@1.0.8: {} - - vscode-languageserver-types@3.16.0: {} + esbuild: 0.21.5 + postcss: 8.4.47 + rollup: 4.24.0 + optionalDependencies: + '@types/node': 22.8.7 + fsevents: 2.3.3 + terser: 5.17.1 - vscode-languageserver@7.0.0: - dependencies: - vscode-languageserver-protocol: 3.16.0 + vitest@2.1.3(@types/node@22.8.7)(@vitest/browser@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(terser@5.17.1): + dependencies: + '@vitest/expect': 2.1.3 + '@vitest/mocker': 2.1.3(@vitest/spy@2.1.3)(msw@2.5.1(@types/node@22.8.7)(typescript@4.7.4))(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1)) + '@vitest/pretty-format': 2.1.3 + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + '@vitest/spy': 2.1.3 + '@vitest/utils': 2.1.3 + chai: 5.1.2 + debug: 4.3.7 + magic-string: 0.30.12 + pathe: 1.1.2 + std-env: 3.7.0 + tinybench: 2.9.0 + tinyexec: 0.3.1 + tinypool: 1.0.1 + tinyrainbow: 1.2.0 + vite: 5.4.10(@types/node@22.8.7)(terser@5.17.1) + vite-node: 2.1.3(@types/node@22.8.7)(terser@5.17.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.8.7 + '@vitest/browser': 2.1.3(@types/node@22.8.7)(@vitest/spy@2.1.3)(playwright@1.48.2)(typescript@4.7.4)(vite@5.4.10(@types/node@22.8.7)(terser@5.17.1))(vitest@2.1.3) + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser - vscode-uri@3.0.7: {} + vm-browserify@1.1.2: {} vscode-uri@3.0.8: {} - vue-bundle-renderer@1.0.3: - dependencies: - ufo: 1.1.1 - - vue-devtools-stub@0.1.0: {} - - vue-router@4.1.6(vue@3.2.47): - dependencies: - '@vue/devtools-api': 6.5.0 - vue: 3.2.47 - vue-tsc@2.1.10(typescript@5.6.3): dependencies: '@volar/typescript': 2.4.10 @@ -21527,13 +19796,15 @@ snapshots: '@vue/compiler-sfc': 2.7.14 csstype: 3.1.2 - vue@3.2.47: + vue@3.5.12(typescript@5.6.3): dependencies: - '@vue/compiler-dom': 3.2.47 - '@vue/compiler-sfc': 3.2.47 - '@vue/runtime-dom': 3.2.47 - '@vue/server-renderer': 3.2.47(vue@3.2.47) - '@vue/shared': 3.2.47 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-sfc': 3.5.12 + '@vue/runtime-dom': 3.5.12 + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3)) + '@vue/shared': 3.5.12 + optionalDependencies: + typescript: 5.6.3 walk-up-path@1.0.0: {} @@ -21571,8 +19842,6 @@ snapshots: webpack-sources@3.2.3: {} - webpack-virtual-modules@0.5.0: {} - webpack@5.88.2(esbuild@0.14.49): dependencies: '@types/eslint-scope': 3.7.4 @@ -21632,13 +19901,6 @@ snapshots: which-module@2.0.0: {} - which-module@2.0.1: {} - - which-pm@2.0.0: - dependencies: - load-yaml-file: 0.2.0 - path-exists: 4.0.0 - which-typed-array@1.1.9: dependencies: available-typed-arrays: 1.0.5 @@ -21656,6 +19918,11 @@ snapshots: dependencies: isexe: 2.0.0 + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + wide-align@1.1.5: dependencies: string-width: 4.2.3 @@ -21711,6 +19978,8 @@ snapshots: ws@8.16.0: {} + ws@8.18.0: {} + ws@8.5.0: {} xdg-basedir@4.0.0: {} @@ -21719,10 +19988,6 @@ snapshots: xtend@4.0.2: {} - xxhashjs@0.2.2: - dependencies: - cuint: 0.2.2 - y18n@4.0.3: {} y18n@5.0.8: {} @@ -21737,12 +20002,9 @@ snapshots: yaml@2.2.1: {} - yargs-parser@15.0.3: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 + yaml@2.6.0: {} - yargs-parser@18.1.3: + yargs-parser@15.0.3: dependencies: camelcase: 5.3.1 decamelize: 1.2.0 @@ -21767,20 +20029,6 @@ snapshots: y18n: 4.0.3 yargs-parser: 15.0.3 - yargs@15.4.1: - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - yargs@17.5.1: dependencies: cliui: 7.0.4 @@ -21810,13 +20058,7 @@ snapshots: yocto-queue@0.1.0: {} - zhead@2.0.4: {} - - zip-stream@4.1.0: - dependencies: - archiver-utils: 2.1.0 - compress-commons: 4.1.1 - readable-stream: 3.6.1 + yoctocolors-cjs@2.1.2: {} zod@3.13.4: {} diff --git a/turbo.json b/turbo.json index 6b167f78..67608ccc 100644 --- a/turbo.json +++ b/turbo.json @@ -9,7 +9,6 @@ }, "test": { "cache": false, - "dependsOn": ["build"], "outputs": [] }, "test:coverage": {