From ebcb762767074975dbb36481520e0f491e04f651 Mon Sep 17 00:00:00 2001 From: Lukas Bestle Date: Mon, 5 Sep 2022 22:22:16 +0200 Subject: [PATCH] Initial commit --- .codecov.yml | 39 + .composer-require-checker.json | 17 + .editorconfig | 19 + .eslintrc.js | 24 + .gitattributes | 30 + .github/CONTRIBUTING.md | 75 + .github/ISSUE_TEMPLATE/bug_report.md | 28 + .github/ISSUE_TEMPLATE/enhancement.md | 13 + .github/ISSUE_TEMPLATE/feature_request.md | 17 + .github/PULL_REQUEST_TEMPLATE.md | 15 + .github/SECURITY.md | 14 + .github/workflows/ci.yml | 249 + .gitignore | 13 + .php-cs-fixer.dist.php | 58 + .prettierrc.json | 4 + LICENSE.md | 21 + README.md | 51 + assets/configurator.js | 17 + composer.json | 54 + composer.lock | 843 ++++ etc/psalm-plugins/HelperFunctionUsePlugin.php | 46 + index.css | 1 + index.js | 3 + index.php | 63 + package-lock.json | 4206 +++++++++++++++++ package.json | 22 + phpmd.xml.dist | 33 + phpunit.xml.dist | 24 + psalm.xml.dist | 26 + screenshot.png | Bin 0 -> 75959 bytes src/classes/Configuration.php | 224 + src/classes/ConfiguratorBlock.php | 202 + src/classes/ConfiguratorVariant.php | 43 + src/classes/Parameter.php | 107 + src/classes/Parameters.php | 32 + src/classes/Part.php | 112 + src/config/blockModels.php | 7 + src/config/blueprints.php | 5 + .../blueprints/blocks/roomle-configurator.yml | 162 + src/config/i18n/de.php | 6 + src/config/i18n/en.php | 6 + src/config/options.php | 16 + src/config/snippets.php | 8 + .../snippets/blocks/roomle-configurator.php | 38 + src/config/snippets/roomle/configuration.php | 8 + src/config/snippets/roomle/parameter.php | 2 + src/config/snippets/roomle/part.php | 5 + src/config/translations.php | 9 + src/frontend/components/Configurator.vue | 133 + src/frontend/components/LazyImage.vue | 30 + src/frontend/icons/roomle.svg | 2 + src/frontend/index.js | 15 + src/frontend/public/configurator.js | 213 + stubs/App.php | 22 + tests/Roomle/ConfigurationTest.php | 277 ++ tests/Roomle/ConfiguratorBlockTest.php | 770 +++ tests/Roomle/ConfiguratorVariantTest.php | 146 + tests/Roomle/ParameterTest.php | 133 + tests/Roomle/ParametersTest.php | 67 + tests/Roomle/PartTest.php | 147 + tests/Roomle/fixtures/configuration.txt | 11 + tests/Roomle/fixtures/parameter.txt | 1 + tests/Roomle/fixtures/part.txt | 3 + tests/bootstrap.php | 10 + 64 files changed, 8997 insertions(+) create mode 100644 .codecov.yml create mode 100644 .composer-require-checker.json create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100644 .gitattributes create mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/enhancement.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/SECURITY.md create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 .prettierrc.json create mode 100644 LICENSE.md create mode 100644 README.md create mode 100644 assets/configurator.js create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 etc/psalm-plugins/HelperFunctionUsePlugin.php create mode 100644 index.css create mode 100644 index.js create mode 100644 index.php create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 phpmd.xml.dist create mode 100644 phpunit.xml.dist create mode 100644 psalm.xml.dist create mode 100644 screenshot.png create mode 100644 src/classes/Configuration.php create mode 100644 src/classes/ConfiguratorBlock.php create mode 100644 src/classes/ConfiguratorVariant.php create mode 100644 src/classes/Parameter.php create mode 100644 src/classes/Parameters.php create mode 100644 src/classes/Part.php create mode 100644 src/config/blockModels.php create mode 100644 src/config/blueprints.php create mode 100644 src/config/blueprints/blocks/roomle-configurator.yml create mode 100644 src/config/i18n/de.php create mode 100644 src/config/i18n/en.php create mode 100644 src/config/options.php create mode 100644 src/config/snippets.php create mode 100644 src/config/snippets/blocks/roomle-configurator.php create mode 100644 src/config/snippets/roomle/configuration.php create mode 100644 src/config/snippets/roomle/parameter.php create mode 100644 src/config/snippets/roomle/part.php create mode 100644 src/config/translations.php create mode 100644 src/frontend/components/Configurator.vue create mode 100644 src/frontend/components/LazyImage.vue create mode 100644 src/frontend/icons/roomle.svg create mode 100644 src/frontend/index.js create mode 100644 src/frontend/public/configurator.js create mode 100644 stubs/App.php create mode 100644 tests/Roomle/ConfigurationTest.php create mode 100644 tests/Roomle/ConfiguratorBlockTest.php create mode 100644 tests/Roomle/ConfiguratorVariantTest.php create mode 100644 tests/Roomle/ParameterTest.php create mode 100644 tests/Roomle/ParametersTest.php create mode 100644 tests/Roomle/PartTest.php create mode 100644 tests/Roomle/fixtures/configuration.txt create mode 100644 tests/Roomle/fixtures/parameter.txt create mode 100644 tests/Roomle/fixtures/part.txt create mode 100644 tests/bootstrap.php diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..8622e3f --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,39 @@ +coverage: + status: + default_rules: + # don't send status checks that don't have flag coverage uploaded + flag_coverage_not_uploaded_behavior: exclude + + # rules for overall project-wide coverage + project: + backend: + flags: + - backend + target: auto + threshold: 1% + frontend: + flags: + - frontend + target: auto + informational: true # don't fail because of frontend tests for now + + # rules based on the specific changes of the PR + patch: + backend: + flags: + - backend + target: 100% + threshold: 2% + frontend: + flags: + - frontend + target: auto + informational: true # don't fail because of frontend tests for now + +flags: + backend: + paths: + - src/classes/ + frontend: + paths: + - src/frontend/ diff --git a/.composer-require-checker.json b/.composer-require-checker.json new file mode 100644 index 0000000..b8eb236 --- /dev/null +++ b/.composer-require-checker.json @@ -0,0 +1,17 @@ +{ + "symbol-whitelist": [], + "php-core-extensions": [ + "Core", + "date", + "json", + "pcre", + "Phar", + "Reflection", + "SPL", + "standard" + ], + "scan-files": [ + "src/**/*.php", + "index.php" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..6e1d9a7 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +[*] +charset = utf-8 +end_of_line = lf +indent_style = tab +indent_size = 2 +trim_trailing_whitespace = true + +[*.php] +indent_size = 4 +insert_final_newline = true + +[*.yml] +indent_style = space + +[*.md,*.txt] +trim_trailing_whitespace = false diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..a80ac4d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,24 @@ +module.exports = { + extends: [ + "eslint:recommended", + "plugin:vue/recommended", + "prettier" + ], + globals: { + "panel": true + }, + rules: { + "vue/attributes-order": "error", + "vue/component-definition-name-casing": "off", + "vue/html-closing-bracket-newline": [ + "error", + { + singleline: "never", + multiline: "always" + } + ], + "vue/multi-word-component-names": "off", + "vue/require-default-prop": "off", + "vue/require-prop-types": "error" + } +}; diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..832426c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,30 @@ +# Git +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore + +# Source files +src/frontend/ export-ignore + +# Development files +.editorconfig export-ignore +.eslintrc.js export-ignore +.prettierrc.json export-ignore +composer.lock export-ignore +package-lock.json export-ignore +package.json export-ignore +rollup.config.js export-ignore + +# Screenshots +screenshot.png export-ignore + +# Tests +.codecov.yml export-ignore +.composer-require-checker.json export-ignore +.php-cs-fixer.dist.php export-ignore +phpmd.xml.dist export-ignore +phpunit.xml.dist export-ignore +psalm.xml.dist export-ignore +etc/ export-ignore +stubs/ export-ignore +tests/ export-ignore diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md new file mode 100644 index 0000000..dc41407 --- /dev/null +++ b/.github/CONTRIBUTING.md @@ -0,0 +1,75 @@ +# Contributing to the Roomle plugin + +👍️🎉 First off, thanks for taking the time to contribute! 🎉👍️ + +There are a few ways how you can contribute to the development of the Roomle plugin: + +## Report issues & suggest features + +While the Roomle plugin was originally created for a client project, the goal is to make it useful for other projects as well. For that I need to know if something isn't working or if something can be improved. + +It's already a huge help to receive detailed bug reports and thought-through feature requests. You can submit any of these by [creating a new issue](https://github.com/lukasbestle/kirby-roomle/issues/new) and using the issue templates, which are set up to guide you through providing all information. The more relevant information you give, the easier it is to find a solution for the bug or feature/enhancement request. + +## Help with translations + +The Panel interface of the Roomle plugin uses the configured user language for all the displayed text. Of course I don't speak all languages of the world, so I need your help. + +You can contribute additional translations by forking this repo and editing the following files: + +- Most importantly, add the translation to the block blueprint in [`src/config/blueprints/blocks/roomle-configurator.yml`](https://github.com/lukasbestle/kirby-roomle/blob/main/src/config/blueprints/blocks/roomle-configurator.yml). +- Add a new file in the [`src/config/i18n` directory](https://github.com/lukasbestle/kirby-roomle/tree/main/src/config/i18n). You can copy any of the existing translation files and use them as the base (however the English translation in `en.php` is considered the "original" and complete version). +- Please also add your translation to the list in [`src/config/translations.php`](https://github.com/lukasbestle/kirby-roomle/blob/main/src/config/translations.php). + +Once you are ready, send a pull request to this repo. + +If you want to improve existing translations, you can change the blueprint and translation files directly and send a pull request. + +Thank you for your help! ❤️ + +## Code contributions + +Pull requests for bug fixes or enhancements are always welcome! + +Please note that I can't guarantee that your pull request will be merged, especially if it's for a larger feature. If you want to make sure, please create an issue first (or comment in an existing issue, if one already exists for the task you want to work on) to discuss your idea. + +### How to set up the project for local development + +If you want to contribute, please first fork this repo. + +Now set up a local installation of one of the Kirby kits (I recommend the [Starterkit](https://github.com/getkirby/starterkit) or [Demokit](https://github.com/getkirby/demokit)). Create the directory `/site/plugins` if it doesn't already exist and then clone your fork of the Roomle plugin into `/site/plugins/roomle`. + +If you want to work on Panel code, please run the following commands inside `/site/plugins/roomle`: + +```sh +npm install +npm run dev +``` + +This will run the kirbyup bundler, which will listen for changes to the files inside the `src/frontend` directory. You can now open the Panel of your Kirby installation. + +If you want to work on the frontend code for the block, please run the following commands: + +```sh +npm install +npm run build:public +``` + +If you want to work on backend code and want to run the automated tests, you need the following command: + +```sh +composer install +``` + +The tests assume that you have PHPUnit installed globally. I also use a few other analysis tools that each have their commands listed in `/composer.json`. With `composer ci` you can run all tools at once (which assumes that all tools have been installed globally). If you don't want to install tools, don't worry – all tools will also be run automatically once you create your pull request. + +**Note:** Never commit the changes to the compiled dist files `/index.css`, `/index.js` and `/assets/*`. Including these files in your PR will lead to merge conflicts down the road. Instead, I will build the dist files for each plugin release. + +## Monetary support + +Most of my development time for this plugin has already been paid for, which is why I'm offering the plugin for free under the terms of the MIT license. For the same reason I do not sell licenses or accept donations. + +However you can support my work by commissioning the development of a feature of your choice: If your project requires a specific feature, you can pay me to build it for you. In exchange for your support you will get the feature more quickly and it will be designed with your requirements in mind. Additionally I can mention you as the sponsor of the feature in the Credits section of the plugin's `README`. + +Features developed in this way will also be published to this repo so that the community can benefit from them as well. This approach also enables future improvements and fixes to the feature. + +If you are interested, please get in touch directly via the [Kirby Forum](https://forum.getkirby.com/u/lukasbestle), [Discord](https://chat.getkirby.com) or [email](mailto:project-kirbyroomle@codesignd.de) to discuss the details. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..fe9c475 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: "🐛 Bug report" +about: "Something that isn't working as expected 🤬" +labels: "type: bug 🐛" +--- + +## Describe the bug + + +## Steps to reproduce + + +## Expected behavior + + +## Additional context + + +## Technical details + +- Plugin version: +- Kirby version: +- Browser: +- Server operating system: diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md new file mode 100644 index 0000000..adf3555 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement.md @@ -0,0 +1,13 @@ +--- +name: "✨ Enhancement" +about: "It exists in the Roomle plugin, but you think it should be better 🤓" +labels: "type: enhancement ✨" +--- + +## Which aspect in the Roomle plugin bothers you and why? + + +## How would you improve it? + + +## How would this make the Roomle plugin better? diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..b2fafe4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,17 @@ +--- +name: "⭐️ Feature request" +about: "Something you would like to see added 🤩" +labels: "type: feature ⭐️" +--- + +## Is your feature request related to a problem? Please describe. + + +## Solution you'd like + + +## Potential alternatives + + +## Additional context + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..408c32b --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,15 @@ +## Describe the PR + + +## Related issues + + +- Fixes # + +## Ready? + + +- [ ] Added unit tests for fixed bug/feature +- [ ] Added in-code documentation (if needed) +- [ ] CI passes (runs automatically when the PR is created or run `composer ci` locally) + Running locally requires PHPUnit, PHP-CS-Fixer, Psalm, PHPCPD and PHPMD. diff --git a/.github/SECURITY.md b/.github/SECURITY.md new file mode 100644 index 0000000..570905a --- /dev/null +++ b/.github/SECURITY.md @@ -0,0 +1,14 @@ +# Security Policy + +I take the security of my open-source projects seriously. If you believe you found a security vulnerability, please report it to me as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please send me an email to . + +If you want to encrypt your message, you can use my PGP key: +[`D703 9FD2 4AAE 69E9 97A6 92FF 2E2A DD32 FE50 61C1`](https://lukasbestle.com/pgp.asc) + +I will get back to you as soon as possible. Please give me some time to fix the issue before publishing your findings (Coordinated Vulnerability Disclosure). diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bfe35f5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,249 @@ +name: CI +on: [push, pull_request] + +jobs: + tests: + name: PHP ${{ matrix.php }} (${{ matrix.dependencies }} deps) + + runs-on: ubuntu-latest + timeout-minutes: 5 + strategy: + matrix: + php: ["8.0", "8.1"] + dependencies: ["lowest", "locked"] + env: + extensions: mbstring, pcov + ini: pcov.directory=., "pcov.exclude=\"~(vendor|tests)~\"" + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Setup PHP cache environment + id: ext-cache + uses: shivammathur/cache-extensions@fc01a9cdc93341e96c2078d848f2e96240d83c17 # pin@v1 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + key: php-v1 + + - name: Cache PHP extensions + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.ext-cache.outputs.dir }} + key: ${{ steps.ext-cache.outputs.key }} + restore-keys: ${{ steps.ext-cache.outputs.key }} + + - name: Setup PHP environment + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + php-version: ${{ matrix.php }} + extensions: ${{ env.extensions }} + ini-values: ${{ env.ini }} + coverage: pcov + tools: phpunit:9.5.13, psalm:4.11.2 + + - name: Setup problem matchers + run: | + echo "::add-matcher::${{ runner.tool_cache }}/php.json" + echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Get Composer cache directory + id: composerCache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.composerCache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-${{ matrix.dependencies }}- + + - name: Install dependencies + run: | + # install the lowest or highest versions based on the matrix config + command=$([ "${{ matrix.dependencies }}" == "lowest" ] && echo "update --prefer-lowest" || echo "install") + composer $command --prefer-dist + + - name: Cache analysis data + id: finishPrepare + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ~/.cache/psalm + key: backend-analysis-${{ matrix.php }}-v2 + + - name: Run tests + if: always() && steps.finishPrepare.outcome == 'success' + run: phpunit --coverage-clover ${{ github.workspace }}/clover.xml + + - name: Statically analyze using Psalm + if: always() && steps.finishPrepare.outcome == 'success' + run: psalm --output-format=github --php-version=${{ matrix.php }} + + - name: Upload coverage results to Codecov + uses: codecov/codecov-action@66b3de25f6f91f65eb92c514d31d6b6f13d5ab18 # pin@v3 + with: + file: ${{ github.workspace }}/clover.xml + flags: backend + env_vars: PHP + env: + PHP: ${{ matrix.php }} + + analysis: + name: Analysis + + runs-on: ubuntu-latest + timeout-minutes: 5 + env: + php: "8.0" + extensions: mbstring + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Setup PHP cache environment + id: ext-cache + uses: shivammathur/cache-extensions@fc01a9cdc93341e96c2078d848f2e96240d83c17 # pin@v1 + with: + php-version: ${{ env.php }} + extensions: ${{ env.extensions }} + key: php-v1 + + - name: Cache PHP extensions + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.ext-cache.outputs.dir }} + key: ${{ steps.ext-cache.outputs.key }} + restore-keys: ${{ steps.ext-cache.outputs.key }} + + - name: Setup PHP environment + id: finishPrepare + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + php-version: ${{ env.php }} + extensions: ${{ env.extensions }} + coverage: none + tools: | + composer:2.3.7, composer-normalize:2.28.0, + composer-unused:0.7.12, phpcpd:6.0.3, phpmd:2.12.0 + + - name: Validate composer.json/composer.lock + if: always() && steps.finishPrepare.outcome == 'success' + run: composer validate --strict --no-check-version --no-check-all + + - name: Ensure that composer.json is normalized + if: always() && steps.finishPrepare.outcome == 'success' + run: composer-normalize --dry-run + + - name: Get Composer cache directory + id: composerCache1 + if: always() && steps.finishPrepare.outcome == 'success' + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache dependencies + id: composerCache2 + if: always() && steps.composerCache1.outcome == 'success' + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ${{ steps.composerCache1.outputs.dir }} + key: ${{ runner.os }}-composer-locked-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer-locked- + + - name: Install dependencies + id: composerInstall + if: always() && steps.composerCache2.outcome == 'success' + run: composer install --prefer-dist + + - name: Check for unused Composer dependencies + if: always() && steps.composerInstall.outcome == 'success' + run: composer-unused --no-progress + + - name: Check for duplicated code + if: always() && steps.composerInstall.outcome == 'success' + run: phpcpd --fuzzy --exclude tests --exclude vendor . + + - name: Statically analyze using PHPMD + if: always() && steps.composerInstall.outcome == 'success' + run: phpmd . github phpmd.xml.dist --exclude 'node_modules/*,stubs/*,tests/*,vendor/*' + + coding-style: + name: Coding Style & Frontend Analysis + + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Checkout + uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b # pin@v3 + + - name: Set up Node.js problem matchers and cache npm dependencies + uses: actions/setup-node@eeb10cff27034e7acf239c5d29f62154018672fd # pin@v3 + with: + cache: 'npm' + + - name: Install npm dependencies + run: npm ci + + - name: Setup PHP environment + uses: shivammathur/setup-php@3eda58347216592f618bb1dff277810b6698e4ca # pin@v2 + with: + coverage: none + tools: php-cs-fixer:3.8.0 + + - name: Cache analysis data + id: finishPrepare + uses: actions/cache@c3f1317a9e7b1ef106c153ac8c0f00fed3ddbc0d # pin@v3 + with: + path: ~/.php-cs-fixer + key: coding-style + + - name: Check for JavaScript coding style violations (ESLint) + if: always() && steps.finishPrepare.outcome == 'success' + # Use the --no-fix flag in push builds to get a failed CI status + run: > + npm run lint -- --max-warnings 0 --format stylish + ${{ github.event_name != 'pull_request' && '--no-fix' || '' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: ESLint + fail_on_error: "true" + + - name: Check for JavaScript coding style violations (Prettier) + if: always() && steps.finishPrepare.outcome == 'success' + # Use the --check flag in push builds to get a failed CI status + run: > + npm run format -- + ${{ github.event_name != 'pull_request' && '--check' || '--write' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: Prettier + fail_on_error: "true" + + - name: Check for PHP coding style violations + if: always() && steps.finishPrepare.outcome == 'success' + env: + PHP_CS_FIXER_IGNORE_ENV: 1 + # Use the --dry-run flag in push builds to get a failed CI status + run: > + php-cs-fixer fix --diff + ${{ github.event_name != 'pull_request' && '--dry-run' || '' }} + + - name: Create code suggestions from the coding style changes (on PR only) + if: > + always() && steps.finishPrepare.outcome == 'success' && + github.event_name == 'pull_request' + uses: reviewdog/action-suggester@ab82daa6ea9b84fe43db7747bb10fa087f34e1ab # pin@v1 + with: + tool_name: PHP-CS-Fixer + fail_on_error: "true" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8322fd4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +# OS files +.DS_Store + +# Vendor files +/node_modules +/vendor + +# Cache and temporary files +/.cache +/.php-cs-fixer.cache +/.phpunit.result.cache +/tests/coverage +/tests/*/tmp diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..87c0710 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,58 @@ +exclude('node_modules') + ->in(__DIR__); + +$config = new PhpCsFixer\Config(); +return $config + ->setRules([ + '@PSR12' => true, + 'align_multiline_comment' => ['comment_type' => 'phpdocs_like'], + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'cast_spaces' => ['space' => 'none'], + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'concat_space' => ['spacing' => 'one'], + 'declare_equal_normalize' => ['space' => 'single'], + 'dir_constant' => true, + 'function_typehint_space' => true, + 'include' => true, + 'logical_operators' => true, + 'lowercase_cast' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'method_chaining_indentation' => true, + 'modernize_types_casting' => true, + 'multiline_comment_opening_closing' => true, + 'native_function_casing' => true, + 'native_function_type_declaration_casing' => true, + 'new_with_braces' => true, + 'no_blank_lines_after_class_opening' => true, + 'no_blank_lines_after_phpdoc' => true, + 'no_empty_comment' => true, + 'no_empty_phpdoc' => true, + 'no_empty_statement' => true, + 'no_leading_namespace_whitespace' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_unneeded_control_parentheses' => true, + 'no_unused_imports' => true, + 'no_useless_return' => true, + 'ordered_class_elements' => ['sort_algorithm' => 'alpha'], + 'ordered_imports' => ['sort_algorithm' => 'alpha'], + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_indent' => true, + 'phpdoc_scalar' => true, + 'phpdoc_trim' => true, + 'short_scalar_cast' => true, + 'single_line_comment_style' => true, + 'single_quote' => true, + 'ternary_to_null_coalescing' => true, + 'whitespace_after_comma_in_array' => true + ]) + ->setRiskyAllowed(true) + ->setIndent("\t") + ->setFinder($finder); diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..2e85942 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,4 @@ +{ + "trailingComma": "none", + "useTabs": true +} diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..4fd1414 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2022 Lukas Bestle + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..33ad800 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +# Roomle for Kirby 3 + +[![Kirby 3.7.0+](https://img.shields.io/badge/Kirby-3.7.0%2B-green)](https://getkirby.com) +[![MIT license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE.md) +[![Release](https://img.shields.io/github/v/release/lukasbestle/kirby-roomle)](https://github.com/lukasbestle/kirby-roomle/releases/latest) +[![CI Status](https://img.shields.io/github/workflow/status/lukasbestle/kirby-roomle/CI?label=CI)](https://github.com/lukasbestle/kirby-roomle/actions?query=workflow%3ACI) +[![Coverage Status](https://img.shields.io/codecov/c/gh/lukasbestle/kirby-roomle?token=IBYEIB22SM)](https://codecov.io/gh/lukasbestle/kirby-roomle) + +> Block to embed the [Roomle 3D Configurator](https://www.roomle.com/en/configurator) into your Kirby site + +![Screenshot of the Roomle block in the Kirby Panel](screenshot.png) + +## Support my work + +> The Roomle plugin is completely free and published under the terms of the MIT license. I do not sell licenses or accept donations, but I'm available for contract work regarding feature development for this plugin. +> ➯ [Read more…](.github/CONTRIBUTING.md#monetary-support) + +## About this plugin + +Roomle Rubens is a 3D product configurator for furniture. Roomle provides the configurator as a hosted web service. + +This Kirby plugin allows you to easily embed the Rubens configurator into your Kirby site. To use the plugin with your own furniture products, you need paid account at [Roomle](https://www.roomle.com/en). + +## Features + +- Customizable variant switcher for different suggested configuration variants +- Automatic handling of the product configuration data for integration in contact forms or shops +- Deeplink generation to allow sharing/bookmarking a specific configuration draft +- Automatic language control based on the site language (only in multilang installations) +- Support for advanced customization of the configurator options + +## Requirements + +- Kirby 3.7.0+ +- PHP 8.0+ + +## Documentation + +The [plugin documentation](https://github.com/lukasbestle/kirby-roomle/wiki) will show you how to set up the plugin initially, how to configure the embedded configurator and how to use the plugin. + +## License + +[The MIT License](LICENSE.md) + +## Contributing & Monetary Support + +See [`CONTRIBUTING.md`](.github/CONTRIBUTING.md). + +## Credits + +- Author and developer: [Lukas Bestle](https://lukasbestle.com) diff --git a/assets/configurator.js b/assets/configurator.js new file mode 100644 index 0000000..df45012 --- /dev/null +++ b/assets/configurator.js @@ -0,0 +1,17 @@ +var m=(t,e,s)=>new Promise((r,i)=>{var o=c=>{try{a(s.next(c))}catch(l){i(l)}},n=c=>{try{a(s.throw(c))}catch(l){i(l)}},a=c=>c.done?r(c.value):Promise.resolve(c.value).then(o,n);a((s=s.apply(t,e)).next())});var y=class{constructor(e,s,r,i){this._outgoingMessageBus=null,this._execMessage=null,this._side=e,this._incomingMessageBus=s,this._outgoingMessageBus=r,this._execMessage=i,this._incomingMessageBus.addEventListener("message",this._handleMessage.bind(this))}setOutgoingMessageBus(e){this._outgoingMessageBus=e}setMessageExecution(e){this._execMessage=e}sendMessage(e,s=[]){return new Promise((r,i)=>{let o=new MessageChannel;o.port1.onmessage=a=>{if(!a||!a.data)return o.port1.close(),o.port2.close(),i(new Error(this._side+" received message but response can not be interpreted"));let c;try{c=JSON.parse(a.data)}catch(l){return o.port1.close(),o.port2.close(),this._prepareError(l),i(l)}c.error?i(c.error):c.result!==void 0?r(c.result):r(),o.port1.close(),o.port2.close()};let n="";try{n=JSON.stringify({message:e,args:s})}catch(a){return i(new Error(this._side+": can not create command because it is not JSON.stringify able"))}if(!this._outgoingMessageBus)return i(new Error(this._side+": outgoing bus not set yet"));this._outgoingMessageBus.postMessage(n,"*",[o.port2])})}_handleMessage(e){let s=e.ports&&Array.isArray(e.ports)&&e.ports.length>0?e.ports[0]:null;if(e.data&&s)try{let r=JSON.parse(e.data);if(!this._execMessage)return s.postMessage(JSON.stringify({error:this._side+" is not ready to handle messages"}));Array.isArray(r.args)||(r.args=[r.args]);let i=this._execMessage(r,e);if(i===void 0)return;i.then((o={})=>{let n,a;typeof o=="object"&&o!==null&&(n=o.error,a=o.result),n?s.postMessage(JSON.stringify({error:n})):a!==void 0?s.postMessage(JSON.stringify({result:a})):s.postMessage(JSON.stringify({result:o}))},o=>{s.postMessage(JSON.stringify({error:this._prepareError(o)}))})}catch(r){s.postMessage(JSON.stringify({error:this._prepareError(r)}))}}_prepareError(e){if(typeof e=="string"){let s=this._side+": "+e;return console.error(s),s}return e.message=this._side+": "+e.message,console.error(e),e.message}},w=".",I={REQUEST_BOOT:"requestBoot",SETUP:"setup",WEBSITE_READY:"websiteReady"},C=(t,e)=>m(void 0,null,function*(){if(typeof t!="string")throw new Error('Configurator ID is not a string type: "'+typeof t+'"');let s=e.customApiUrl?e.customApiUrl:"https://api.roomle.com/v2",r=e.overrideTenant||9,i=s+"/configurators/"+t,o="roomle_portal_v2",n="03-"+window.btoa(new Date().toISOString()+";anonymous;"+o),a=()=>{let h={apiKey:o,currentTenant:r,locale:"en",language:"en",device:1,token:n,platform:"web"};return new Headers(h)},c=new Request(i,{method:"GET",headers:a(),mode:"cors",cache:"default"}),l=yield fetch(c),{configurator:g}=yield l.json();return g}),U=()=>{try{return window.self!==window.top}catch(t){return!0}},D=["127.0.0.1","localhost","0.0.0.0"],F=()=>{let t=U(),e=window.location.href;if(t){if(!document.referrer)return null;e=document.referrer}let{hostname:s}=new URL(e);return s},H=t=>!!(D.includes(t)||t.endsWith("roomle.com")||t.endsWith("gitlab.io")||t.endsWith("gitlab.com")),k=(t,e)=>{let s=JSON.parse(JSON.stringify(t));return b(s,e)},b=(t,e)=>{for(let s in e)try{e[s].constructor===Object?t[s]=b(t[s],e[s]):t[s]=e[s]}catch(r){t[s]=e[s]}return t},A=["language","browserLanguage","userLanguage","systemLanguage"],B=(t=null)=>{let e=window.navigator;if(t)return t.substr(0,2);if(Array.isArray(e.languages)&&e.languages.length>0)return e.languages[0].substr(0,2);for(let s=0,r=A.length;s(O(t),t!=null&&t.customApiUrl&&(t.customApiUrl=decodeURIComponent(t.customApiUrl)),t.shareUrl&&(t.deeplink=t.shareUrl.replace(G,$)),t),O=t=>{if(!t)return;let e=Object.keys(t);for(let s of e){let r=t[s];if(!Array.isArray(r)&&typeof r=="object"&&r!==null)return O(r);if(Array.isArray(r)){for(let i of r)O(i);return}(r==="true"||r==="false")&&(t[s]=r==="true")}},J=(t,e)=>{e.configuratorId=t.id;let s=t.settings||{};return!e.overrideTenant&&t.tenant&&(e.overrideTenant=t.tenant),k(s,e)},V=()=>{let t={};t.locale||(t.locale=B()),t.id===x&&delete t.id;let e=F();return e&&H(e)&&(t.configuratorId="demoConfigurator"),t.customApiUrl="https://www.roomle.com/api/v2",t.emails=!1,t},G="",$="#CONFIGURATIONID#",v=()=>/(android)/i.test(navigator.userAgent),R=(t,e,s)=>{let r=null;Object.defineProperty(t,e,{get(){return r||s},set(i){i!=null&&i.mute?r=i.value:(console.warn("You override Roomle defined behaviour. To disalbe this warning pass in an object with the following properties"),console.warn("{ mute: true, value: () => void }"),r=i)}})},N=()=>window.innerHeight*.01+"px",L=t=>{!t||setTimeout(()=>t.style.setProperty(S,N()),0)},T="rml-styles",q=450,S="--rml-full-height",u={CONTAINER:"rml-container",FILL:"rml-fill",POSITION:"rml-pos",TRANSITION:"rml-transition",ANDROID_HEIGHT:"rml-android-height",OVERFLOW_HIDDEN:"rml-overflow-hidden"},E=new Map,_=class{constructor(e,s,r,i){if(this.ui={callbacks:null},this.extended={callbacks:null},this.analytics={callbacks:{}},this.global={callbacks:{}},this._initData={},!e||typeof e.id!="string")throw new Error("Please provide a correct configuratorId, you get the correct ID from your Roomle Contact Person");if(E.has(s))throw new Error("There is already an instance on this DOM element");if(!!!document.getElementById(T)){let a=r.zIndex||9999999,c=document.createElement("style");c.type="text/css",c.id=T;let l="transition:all ease-in-out "+q+"ms;",g=["-webkit-","-o-"].reduce((f,d)=>f+=d+l,"")+l,h=N();c.innerHTML=` + .${u.CONTAINER}{${S}:${h};} + .${u.POSITION}{position:fixed;top:0;left:0;z-index:${a};opacity:0} + .${u.TRANSITION}{${g}} + .${u.FILL}{width:100%;height:100%;opacity:1} + .${u.ANDROID_HEIGHT}{height:calc(var(${S},1vh)*100)} + .${u.OVERFLOW_HIDDEN}{overflow:hidden} + `,document.head.appendChild(c)}this._onResize=this._onResize.bind(this),v()&&window.addEventListener("resize",this._onResize),this._container=s,this._initData=r,this._configuratorSettings=e;let n=this._createIframe();this._onUseFullPage=this._onUseFullPage.bind(this),this._executeMessage=this._executeMessage.bind(this),this._onBackToWebsite=this._onBackToWebsite.bind(this),this._messageHandler=new y("website",window,null,this._executeMessage),this._waitForIframe=i,this._container.appendChild(n),this._iframe=n,E.set(s,!0)}static createPlanner(e,s,r){return this._create(e,s,r)}static createConfigurator(e,s,r){return this._create(e,s,r)}static create(e,s,r){return this._create(e,s,r)}static createViewer(e,s,r){return this._create(e,s,r)}static _create(e,s,r){return new Promise((i,o)=>m(this,null,function*(){try{let n=b(V(),W(r));n.featureFlags||(n.featureFlags={}),typeof n.featureFlags.realPartList!="boolean"&&(n.featureFlags.realPartList=!0),typeof n.featureFlags.globalCallbacks!="boolean"&&(n.featureFlags.globalCallbacks=!0);let a=yield C(e,n);return r=J(a,n),new this(a,s,r,i)}catch(n){return o(n)}}))}teardown(){this._container&&E.delete(this._container);let e=this._container.querySelector("iframe");e&&this._container.removeChild(e),window.removeEventListener("resize",this._onResize)}_createIframe(){var e;let s=document.createElement("iframe"),r=((e=this._configuratorSettings)===null||e===void 0?void 0:e.url)||"https://www.roomle.com/t/cp/";return this._initData.useLocalRoomle&&(r=location.href.replace("embedding.html","")),location.href.includes("roomle.gitlab.io")&&(r=location.href.replace("embedding.html","index.html")),this._initData.overrideServerUrl&&(r=this._initData.overrideServerUrl),s.src=r,s.classList.add(u.CONTAINER),s.classList.add(u.FILL),s}_onResize(){L(this._iframe)}_onUseFullPage(){this._iframe.classList.add(u.POSITION),document.documentElement.classList.add(u.OVERFLOW_HIDDEN),window.document.body.classList.add(u.OVERFLOW_HIDDEN),v()&&(L(this._iframe),this._iframe.classList.add(u.ANDROID_HEIGHT))}_onBackToWebsite(){this._iframe.classList.remove(u.POSITION),this._iframe.classList.remove(u.ANDROID_HEIGHT),document.documentElement.classList.remove(u.OVERFLOW_HIDDEN),window.document.body.classList.remove(u.OVERFLOW_HIDDEN)}_executeMessage({message:e,args:s},r){var i;if(!r.source||r.source!==((i=this._iframe)===null||i===void 0?void 0:i.contentWindow))return;if(e===I.REQUEST_BOOT)return this._messageHandler.setOutgoingMessageBus(r.source),Promise.resolve({result:this._initData});if(e===I.SETUP){let{methods:l,callbacks:g}=s[0];return l.forEach(h=>{let f=h.split(w),d=f[0],p=f[1];this[d]||(this[d]={}),this[d][p]=function(){return this._messageHandler.sendMessage(h,[...arguments])}.bind(this)}),g.forEach(h=>{let f=h.split(w),d=f[0],p=f[1],P=f[2];this[d]||(this[d]={}),this[d][p]||(this[d][p]={}),this[d][p][P]=()=>{}}),R(this.ui.callbacks,"onUseFullPage",this._onUseFullPage),R(this.ui.callbacks,"onBackToWebsite",this._onBackToWebsite),this._waitForIframe(this),setTimeout(()=>this._messageHandler.sendMessage(I.WEBSITE_READY),0),Promise.resolve({result:null})}let o=e.split(w),n=o[0],a=o[1],c=o.length===3?o[2]:null;if(c&&this[n][a][c]){let l=this[n][a][c](...s);return l instanceof Promise?l.then(g=>({result:g})):l!==void 0?Promise.resolve({result:l}):Promise.resolve({result:null})}return Promise.reject('Message "'+e+'" is unkown')}};var M=class{constructor(t){this.props=t,this.variantSelector="#"+t.htmlId+" .roomle-configurator-variants input",this.currentId=this.idFromUrl()||t.options.id}init(){return m(this,null,function*(){if(this.configurator)return;document.querySelectorAll(this.variantSelector).forEach(e=>{e.addEventListener("change",this.onVariantSelect.bind(this))}),window.addEventListener("popstate",this.onUrlChange.bind(this)),this.updateVariants(this.currentId);let t=this.props.options;t.id=this.currentId,this.configurator=yield _.createConfigurator(this.props.configuratorId,document.getElementById(this.props.htmlId+"-container"),t),this.currentId!==t.id&&this.load(this.currentId),this.props.targetUrl&&(this.configurator.ui.callbacks.onRequestProduct=this.onRequestProduct.bind(this))})}idFromUrl(){return new URLSearchParams(window.location.search).get(this.props.htmlId)}idToUrl(t,e=!1){let s=new URL(window.location.href);return s.searchParams.set(this.props.htmlId,t),e===!0&&(s.hash=this.props.htmlId),s}load(t){return m(this,null,function*(){return this.currentId=t,this.updateVariants(t),this.configurator?(yield this.configurator.ui.loadObject(t),!0):!1})}onRequestProduct(t,e,s,r,i,o){let n={catalog:o.catalog,configuratorUrl:this.idToUrl(t,!0),depth:o.depth,height:o.height,id:t,label:o.label,parts:s.fullList,perspectiveImage:o.perspectiveImage,rootComponentId:o.rootComponentId,topImage:o.topImage,width:o.width},a=document.createElement("form");a.action=this.props.targetUrl,a.method="POST",a.style.display="none";let c=document.createElement("input");c.name="roomle-configuration",c.value=JSON.stringify(n),a.appendChild(c),document.body.appendChild(a),a.submit()}onUrlChange(){let t=this.idFromUrl();t?this.load(t):this.load(this.props.options.id)}onVariantSelect(t){let e=this.idToUrl(t.target.value);window.history.pushState(null,"",e),this.load(t.target.value)}updateVariants(t){let e=document.querySelector(this.variantSelector+"[checked]"),s=document.querySelector(this.variantSelector+'[value="'+CSS.escape(t)+'"]');e&&(e.removeAttribute("checked"),e.checked=!1),s&&(s.checked=!0,s.setAttribute("checked","true"))}};export{M as default}; +/** + * Configurator + * + * @package Kirby Roomle Plugin + * @author Lukas Bestle + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..2b96ab9 --- /dev/null +++ b/composer.json @@ -0,0 +1,54 @@ +{ + "name": "lukasbestle/kirby-roomle", + "description": "Roomle Plugin for Kirby 3", + "license": "MIT", + "type": "kirby-plugin", + "version": "1.0.0-rc.1", + "authors": [ + { + "name": "Lukas Bestle", + "email": "project-kirbyroomle@lukasbestle.com" + } + ], + "require": { + "php": ">=8.0.0 <8.2.0", + "getkirby/cms": ">=3.7.0 <3.8.0", + "getkirby/composer-installer": "^1.1" + }, + "suggest": { + "ext-intl": "Support for locale-aware number formatting" + }, + "autoload-dev": { + "psr-4": { + "LukasBestle\\": "tests/" + } + }, + "config": { + "allow-plugins": { + "getkirby/composer-installer": true + } + }, + "extra": { + "installer-name": "roomle", + "kirby-cms-path": false + }, + "scripts": { + "analyze": [ + "@analyze:composer", + "@analyze:psalm", + "@analyze:phpcpd", + "@analyze:phpmd" + ], + "analyze:composer": "composer validate --strict --no-check-version --no-check-all", + "analyze:phpcpd": "phpcpd --fuzzy --exclude node_modules --exclude tests --exclude vendor .", + "analyze:phpmd": "phpmd . ansi phpmd.xml.dist --exclude 'node_modules/*,stubs/*,tests/*,vendor/*'", + "analyze:psalm": "psalm", + "ci": [ + "@fix", + "@analyze", + "@test" + ], + "fix": "php-cs-fixer fix", + "test": "phpunit --stderr --coverage-html=tests/coverage" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..143d09a --- /dev/null +++ b/composer.lock @@ -0,0 +1,843 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2a4fdad8181f27c0b7ec75571eb354d2", + "packages": [ + { + "name": "claviska/simpleimage", + "version": "3.7.0", + "source": { + "type": "git", + "url": "https://github.com/claviska/SimpleImage.git", + "reference": "abd15ced313c7b8041d7d73d8d2398b4f2510cf1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/claviska/SimpleImage/zipball/abd15ced313c7b8041d7d73d8d2398b4f2510cf1", + "reference": "abd15ced313c7b8041d7d73d8d2398b4f2510cf1", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "league/color-extractor": "0.3.*", + "php": ">=5.6.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "claviska": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cory LaViska", + "homepage": "http://www.abeautifulsite.net/", + "role": "Developer" + } + ], + "description": "A PHP class that makes working with images as simple as possible.", + "support": { + "issues": "https://github.com/claviska/SimpleImage/issues", + "source": "https://github.com/claviska/SimpleImage/tree/3.7.0" + }, + "funding": [ + { + "url": "https://github.com/claviska", + "type": "github" + } + ], + "time": "2022-07-05T13:18:44+00:00" + }, + { + "name": "filp/whoops", + "version": "2.14.5", + "source": { + "type": "git", + "url": "https://github.com/filp/whoops.git", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/filp/whoops/zipball/a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "reference": "a63e5e8f26ebbebf8ed3c5c691637325512eb0dc", + "shasum": "" + }, + "require": { + "php": "^5.5.9 || ^7.0 || ^8.0", + "psr/log": "^1.0.1 || ^2.0 || ^3.0" + }, + "require-dev": { + "mockery/mockery": "^0.9 || ^1.0", + "phpunit/phpunit": "^4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.3", + "symfony/var-dumper": "^2.6 || ^3.0 || ^4.0 || ^5.0" + }, + "suggest": { + "symfony/var-dumper": "Pretty print complex values better with var-dumper available", + "whoops/soap": "Formats errors as SOAP responses" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Whoops\\": "src/Whoops/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Filipe Dobreira", + "homepage": "https://github.com/filp", + "role": "Developer" + } + ], + "description": "php error handling for cool kids", + "homepage": "https://filp.github.io/whoops/", + "keywords": [ + "error", + "exception", + "handling", + "library", + "throwable", + "whoops" + ], + "support": { + "issues": "https://github.com/filp/whoops/issues", + "source": "https://github.com/filp/whoops/tree/2.14.5" + }, + "funding": [ + { + "url": "https://github.com/denis-sokolov", + "type": "github" + } + ], + "time": "2022-01-07T12:00:00+00:00" + }, + { + "name": "getkirby/cms", + "version": "3.7.5", + "source": { + "type": "git", + "url": "https://github.com/getkirby/kirby.git", + "reference": "021561f7444896fc9917eccb52768a6e715e9a74" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/kirby/zipball/021561f7444896fc9917eccb52768a6e715e9a74", + "reference": "021561f7444896fc9917eccb52768a6e715e9a74", + "shasum": "" + }, + "require": { + "claviska/simpleimage": "3.7.0", + "ext-ctype": "*", + "ext-curl": "*", + "ext-dom": "*", + "ext-filter": "*", + "ext-hash": "*", + "ext-iconv": "*", + "ext-json": "*", + "ext-libxml": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-simplexml": "*", + "filp/whoops": "2.14.5", + "getkirby/composer-installer": "^1.2.1", + "laminas/laminas-escaper": "2.10.0", + "michelf/php-smartypants": "1.8.1", + "php": ">=7.4.0 <8.2.0", + "phpmailer/phpmailer": "6.6.4", + "symfony/polyfill-intl-idn": "1.26.0", + "symfony/polyfill-mbstring": "1.26.0" + }, + "replace": { + "symfony/polyfill-php72": "*" + }, + "suggest": { + "ext-PDO": "Support for using databases", + "ext-apcu": "Support for the Apcu cache driver", + "ext-exif": "Support for exif information from images", + "ext-fileinfo": "Improved mime type detection for files", + "ext-intl": "Improved i18n number formatting", + "ext-memcached": "Support for the Memcached cache driver", + "ext-zip": "Support for ZIP archive file functions", + "ext-zlib": "Sanitization and validation for svgz files" + }, + "type": "kirby-cms", + "extra": { + "unused": [ + "symfony/polyfill-intl-idn" + ] + }, + "autoload": { + "files": [ + "config/setup.php", + "config/helpers.php" + ], + "psr-4": { + "Kirby\\": "src/" + }, + "classmap": [ + "dependencies/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "proprietary" + ], + "authors": [ + { + "name": "Kirby Team", + "email": "support@getkirby.com", + "homepage": "https://getkirby.com" + } + ], + "description": "The Kirby 3 core", + "homepage": "https://getkirby.com", + "keywords": [ + "cms", + "core", + "kirby" + ], + "support": { + "email": "support@getkirby.com", + "forum": "https://forum.getkirby.com", + "issues": "https://github.com/getkirby/kirby/issues", + "source": "https://github.com/getkirby/kirby" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2022-08-30T18:27:48+00:00" + }, + { + "name": "getkirby/composer-installer", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/getkirby/composer-installer.git", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/getkirby/composer-installer/zipball/c98ece30bfba45be7ce457e1102d1b169d922f3d", + "reference": "c98ece30bfba45be7ce457e1102d1b169d922f3d", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0 || ^2.0" + }, + "require-dev": { + "composer/composer": "^1.8 || ^2.0" + }, + "type": "composer-plugin", + "extra": { + "class": "Kirby\\ComposerInstaller\\Plugin" + }, + "autoload": { + "psr-4": { + "Kirby\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Kirby's custom Composer installer for the Kirby CMS and for Kirby plugins", + "homepage": "https://getkirby.com", + "support": { + "issues": "https://github.com/getkirby/composer-installer/issues", + "source": "https://github.com/getkirby/composer-installer/tree/1.2.1" + }, + "funding": [ + { + "url": "https://getkirby.com/buy", + "type": "custom" + } + ], + "time": "2020-12-28T12:54:39+00:00" + }, + { + "name": "laminas/laminas-escaper", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/laminas/laminas-escaper.git", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laminas/laminas-escaper/zipball/58af67282db37d24e584a837a94ee55b9c7552be", + "reference": "58af67282db37d24e584a837a94ee55b9c7552be", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-mbstring": "*", + "php": "^7.4 || ~8.0.0 || ~8.1.0" + }, + "conflict": { + "zendframework/zend-escaper": "*" + }, + "require-dev": { + "infection/infection": "^0.26.6", + "laminas/laminas-coding-standard": "~2.3.0", + "maglnet/composer-require-checker": "^3.8.0", + "phpunit/phpunit": "^9.5.18", + "psalm/plugin-phpunit": "^0.16.1", + "vimeo/psalm": "^4.22.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Laminas\\Escaper\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "Securely and safely escape HTML, HTML attributes, JavaScript, CSS, and URLs", + "homepage": "https://laminas.dev", + "keywords": [ + "escaper", + "laminas" + ], + "support": { + "chat": "https://laminas.dev/chat", + "docs": "https://docs.laminas.dev/laminas-escaper/", + "forum": "https://discourse.laminas.dev", + "issues": "https://github.com/laminas/laminas-escaper/issues", + "rss": "https://github.com/laminas/laminas-escaper/releases.atom", + "source": "https://github.com/laminas/laminas-escaper" + }, + "funding": [ + { + "url": "https://funding.communitybridge.org/projects/laminas-project", + "type": "community_bridge" + } + ], + "time": "2022-03-08T20:15:36+00:00" + }, + { + "name": "league/color-extractor", + "version": "0.3.2", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/color-extractor.git", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/color-extractor/zipball/837086ec60f50c84c611c613963e4ad2e2aec806", + "reference": "837086ec60f50c84c611c613963e4ad2e2aec806", + "shasum": "" + }, + "require": { + "ext-gd": "*", + "php": ">=5.4.0" + }, + "replace": { + "matthecat/colorextractor": "*" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~5" + }, + "type": "library", + "autoload": { + "psr-4": { + "": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mathieu Lechat", + "email": "math.lechat@gmail.com", + "homepage": "http://matthecat.com", + "role": "Developer" + } + ], + "description": "Extract colors from an image as a human would do.", + "homepage": "https://github.com/thephpleague/color-extractor", + "keywords": [ + "color", + "extract", + "human", + "image", + "palette" + ], + "support": { + "issues": "https://github.com/thephpleague/color-extractor/issues", + "source": "https://github.com/thephpleague/color-extractor/tree/master" + }, + "time": "2016-12-15T09:30:02+00:00" + }, + { + "name": "michelf/php-smartypants", + "version": "1.8.1", + "source": { + "type": "git", + "url": "https://github.com/michelf/php-smartypants.git", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/michelf/php-smartypants/zipball/47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "reference": "47d17c90a4dfd0ccf1f87e25c65e6c8012415aad", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Michelf": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Michel Fortin", + "email": "michel.fortin@michelf.ca", + "homepage": "https://michelf.ca/", + "role": "Developer" + }, + { + "name": "John Gruber", + "homepage": "https://daringfireball.net/" + } + ], + "description": "PHP SmartyPants", + "homepage": "https://michelf.ca/projects/php-smartypants/", + "keywords": [ + "dashes", + "quotes", + "spaces", + "typographer", + "typography" + ], + "support": { + "issues": "https://github.com/michelf/php-smartypants/issues", + "source": "https://github.com/michelf/php-smartypants/tree/1.8.1" + }, + "time": "2016-12-13T01:01:17+00:00" + }, + { + "name": "phpmailer/phpmailer", + "version": "v6.6.4", + "source": { + "type": "git", + "url": "https://github.com/PHPMailer/PHPMailer.git", + "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/a94fdebaea6bd17f51be0c2373ab80d3d681269b", + "reference": "a94fdebaea6bd17f51be0c2373ab80d3d681269b", + "shasum": "" + }, + "require": { + "ext-ctype": "*", + "ext-filter": "*", + "ext-hash": "*", + "php": ">=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", + "doctrine/annotations": "^1.2", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^9.3.5", + "roave/security-advisories": "dev-latest", + "squizlabs/php_codesniffer": "^3.6.2", + "yoast/phpunit-polyfills": "^1.0.0" + }, + "suggest": { + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.4" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2022-08-22T09:22:00+00:00" + }, + { + "name": "psr/log", + "version": "3.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001", + "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/3.0.0" + }, + "time": "2021-07-14T16:46:02+00:00" + }, + { + "name": "symfony/polyfill-intl-idn", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "reference": "59a8d271f00dd0e4c2e518104cc7963f655a1aa8", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "symfony/polyfill-intl-normalizer": "^1.10", + "symfony/polyfill-php72": "^1.10" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + }, + { + "name": "Trevor Rowbotham", + "email": "trevor.rowbotham@pm.me" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/219aa369ceff116e673852dce47c3a41794c14bd", + "reference": "219aa369ceff116e673852dce47c3a41794c14bd", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.26.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.26-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-24T11:49:31+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=8.0.0 <8.2.0" + }, + "platform-dev": [], + "plugin-api-version": "2.3.0" +} diff --git a/etc/psalm-plugins/HelperFunctionUsePlugin.php b/etc/psalm-plugins/HelperFunctionUsePlugin.php new file mode 100644 index 0000000..7d69b74 --- /dev/null +++ b/etc/psalm-plugins/HelperFunctionUsePlugin.php @@ -0,0 +1,46 @@ +getExpr(); + if ($expr->name instanceof Expr) { + return; + } + + // find where the called function was defined + $functions = $event->getCodebase()->functions; + $statementsSource = $event->getStatementsSource(); + $storage = $functions->getStorage( + $statementsSource instanceof StatementsAnalyzer ?: null, + $event->getFunctionId() + ); + + // if the function is a Kirby helper, consider this function call an issue + if ($storage->location->file_path === dirname(__FILE__, 3) . '/vendor/getkirby/cms/config/helpers.php') { + IssueBuffer::accepts( + new HelperFunctionUse( + 'Use of user-overridable Kirby helper "' . $storage->cased_name . '"', + new CodeLocation($statementsSource, $expr->name) + ), + $statementsSource->getSuppressedIssues() + ); + } + } +} + +class HelperFunctionUse extends PluginIssue +{ +} diff --git a/index.css b/index.css new file mode 100644 index 0000000..b51dd55 --- /dev/null +++ b/index.css @@ -0,0 +1 @@ +.k-block-type-roomle-configurator-wrapper,.k-block-type-roomle-configurator-variants{display:grid;grid-template-columns:1fr 1fr;grid-gap:1rem}.k-block-type-roomle-configurator-wrapper{align-items:start}.k-block-type-roomle-configurator-variant{display:flex;flex-direction:column;justify-content:space-between}.k-block-type-roomle-configurator-main img,.k-block-type-roomle-configurator-variant img{width:100%}.k-block-type-roomle-configurator-main .k-empty{height:100%}.k-block-type-roomle-configurator-labels{margin-top:.5rem}.k-block-type-roomle-configurator-labels *{display:block}@media screen and (max-width: 40rem){.k-block-type-roomle-configurator-wrapper{display:block}} diff --git a/index.js b/index.js new file mode 100644 index 0000000..44d225e --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +(function(){"use strict";const f=` + +`,w="";function l(t,e,r,s,i,c,u,I){var o=typeof t=="function"?t.options:t;e&&(o.render=e,o.staticRenderFns=r,o._compiled=!0),s&&(o.functional=!0),c&&(o._scopeId="data-v-"+c);var a;if(u?(a=function(n){n=n||this.$vnode&&this.$vnode.ssrContext||this.parent&&this.parent.$vnode&&this.parent.$vnode.ssrContext,!n&&typeof __VUE_SSR_CONTEXT__<"u"&&(n=__VUE_SSR_CONTEXT__),i&&i.call(this,n),n&&n._registeredComponents&&n._registeredComponents.add(u)},o._ssrRegister=a):i&&(a=I?function(){i.call(this,(o.functional?this.parent:this).$root.$options.shadowRoot)}:i),a)if(o.functional){o._injectStyles=a;var T=o.render;o.render=function(U,m){return a.call(m),T(U,m)}}else{var _=o.beforeCreate;o.beforeCreate=_?[].concat(_,a):[a]}return{exports:t,options:o}}const p={computed:{mainUrl(){return this.idToImageUrl(this.content.mainproductid)}},methods:{idToImageUrl(t){return t?t.split(":").length>2?"https://uploads.roomle.com/configurations/"+t+"/perspectiveImage.png":"https://www.roomle.com/api/v2/items/"+t+"/perspectiveImageHD":null},variantToImageUrl(t){return t.image[0]?t.image[0].url:t.productid?this.idToImageUrl(t.productid):null}}};var d=function(){var e=this,r=e._self._c;return r("div",{staticClass:"k-block-type-roomle-configurator-wrapper",on:{dblclick:function(s){return e.$emit("open")}}},[r("k-aspect-ratio",{staticClass:"k-block-type-roomle-configurator-main",attrs:{ratio:"1/1"}},[r("lbro-lazy-image",{attrs:{src:e.mainUrl}},[r("k-empty",{attrs:{icon:"roomle",layout:"cardlets",text:e.$t("roomle.empty")}})],1)],1),r("ul",{staticClass:"k-block-type-roomle-configurator-variants"},e._l(e.content.variants,function(s,i){return r("li",{key:i,staticClass:"k-block-type-roomle-configurator-variant"},[r("lbro-lazy-image",{attrs:{src:e.variantToImageUrl(s)}},[r("k-empty",{attrs:{icon:"image",layout:"cardlets",text:e.$t("roomle.noRendering")}})],1),r("span",{staticClass:"k-block-type-roomle-configurator-labels"},[r("strong",[e._v(e._s(s.title))]),r("span",[e._v(e._s(s.subtitle))])])],1)}),0)],1)},v=[],g=l(p,d,v,!1,null,null,null,null);const h=g.exports,b={props:{alt:{type:String,default:""},src:String},data(){return{visible:!0}}};var y=function(){var e=this,r=e._self._c;return r("span",[e.src?r("img",{directives:[{name:"show",rawName:"v-show",value:e.visible,expression:"visible"}],attrs:{alt:e.alt,src:e.src},on:{error:function(s){e.visible=!1},load:function(s){e.visible=!0}}}):e._e(),!e.src||!e.visible?e._t("default"):e._e()],2)},k=[],C=l(b,y,k,!1,null,null,null,null);const $=C.exports;panel.plugin("lukasbestle/roomle",{blocks:{"roomle-configurator":h},components:{"lbro-lazy-image":$},icons:{roomle:f}})})(); diff --git a/index.php b/index.php new file mode 100644 index 0000000..e3c8759 --- /dev/null +++ b/index.php @@ -0,0 +1,63 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ + +// validate the Kirby version; the supported versions are +// updated manually when verified to work with the plugin +$kirbyVersion = App::version(); +if ( + $kirbyVersion !== null && + ( + version_compare($kirbyVersion, '3.7.0', '<') === true || + version_compare($kirbyVersion, '3.8.0-alpha', '>=') === true + ) +) { + throw new Exception( + 'The installed version of the Kirby Roomle plugin ' . + 'is not compatible with Kirby ' . $kirbyVersion + ); +} + +// autoload classes +F::loadClasses([ + 'LukasBestle\Roomle\Configuration' => __DIR__ . '/src/classes/Configuration.php', + 'LukasBestle\Roomle\ConfiguratorBlock' => __DIR__ . '/src/classes/ConfiguratorBlock.php', + 'LukasBestle\Roomle\ConfiguratorVariant' => __DIR__ . '/src/classes/ConfiguratorVariant.php', + 'LukasBestle\Roomle\Parameter' => __DIR__ . '/src/classes/Parameter.php', + 'LukasBestle\Roomle\Parameters' => __DIR__ . '/src/classes/Parameters.php', + 'LukasBestle\Roomle\Part' => __DIR__ . '/src/classes/Part.php', +]); + +// register the plugin +App::plugin('lukasbestle/roomle', [ + 'blockModels' => require __DIR__ . '/src/config/blockModels.php', + 'blueprints' => require __DIR__ . '/src/config/blueprints.php', + 'options' => require __DIR__ . '/src/config/options.php', + 'snippets' => require __DIR__ . '/src/config/snippets.php', + 'translations' => require __DIR__ . '/src/config/translations.php', +]); + +/** + * Returns the object for a product configuration + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + */ +function roomleConfiguration(array|string|null $data = null): Configuration|null +{ + return Configuration::lazyInstance($data); +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..87e8998 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,4206 @@ +{ + "name": "roomle", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "@roomle/embedding-lib": "^4.41.0" + }, + "devDependencies": { + "esbuild": "^0.15.6", + "eslint": "^8.16.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-vue": "^9.0.1", + "kirbyup": "^2.0.0", + "prettier": "^2.6.2" + } + }, + "node_modules/@antfu/utils": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.5.2.tgz", + "integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/parser": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", + "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@roomle/embedding-lib": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@roomle/embedding-lib/-/embedding-lib-4.42.0.tgz", + "integrity": "sha512-jnYIwJjKrqZwqLFzo6BgEmZ/r/cZ8XTKyqsU8tH3t1XuMI4IwfHn/RfoeBZPJv29pDuEc6clMNk9DWG3KE7GCg==", + "dependencies": { + "@roomle/web-sdk": "2.38.0" + } + }, + "node_modules/@roomle/web-sdk": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@roomle/web-sdk/-/web-sdk-2.38.0.tgz", + "integrity": "sha512-1NQOjarzfq1DzmpidyjGRiMmJZSQ4UgHLQSSkVI8dierzx6A70NKo28+tHhFCuM1ZvVK3UeDhRIk02xC0NtoHw==", + "hasInstallScript": true + }, + "node_modules/@vue/compiler-sfc": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz", + "integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "node_modules/acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/defu": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.0.tgz", + "integrity": "sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==", + "dev": true + }, + "node_modules/detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "dependencies": { + "execa": "^5.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/esbuild": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.7.tgz", + "integrity": "sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.15.7", + "esbuild-android-64": "0.15.7", + "esbuild-android-arm64": "0.15.7", + "esbuild-darwin-64": "0.15.7", + "esbuild-darwin-arm64": "0.15.7", + "esbuild-freebsd-64": "0.15.7", + "esbuild-freebsd-arm64": "0.15.7", + "esbuild-linux-32": "0.15.7", + "esbuild-linux-64": "0.15.7", + "esbuild-linux-arm": "0.15.7", + "esbuild-linux-arm64": "0.15.7", + "esbuild-linux-mips64le": "0.15.7", + "esbuild-linux-ppc64le": "0.15.7", + "esbuild-linux-riscv64": "0.15.7", + "esbuild-linux-s390x": "0.15.7", + "esbuild-netbsd-64": "0.15.7", + "esbuild-openbsd-64": "0.15.7", + "esbuild-sunos-64": "0.15.7", + "esbuild-windows-32": "0.15.7", + "esbuild-windows-64": "0.15.7", + "esbuild-windows-arm64": "0.15.7" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", + "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", + "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", + "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", + "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", + "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", + "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", + "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", + "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", + "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", + "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", + "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", + "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", + "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", + "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", + "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", + "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", + "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", + "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", + "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", + "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "dependencies": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.4.0.tgz", + "integrity": "sha512-Nzz2QIJ8FG+rtJaqT/7/ru5ie2XgT9KCudkbN0y3uFYhQ41nuHEaboLAiqwMcK006hZPQv/rVMRhUIwEGhIvfQ==", + "dev": true, + "dependencies": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.0.1", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.14.0.tgz", + "integrity": "sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/kirbyup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kirbyup/-/kirbyup-2.0.1.tgz", + "integrity": "sha512-Yhjakz09gIqTUKZHK4EmU+sYK5H0aV//+WYThwzIWPTSzvS5iKT319wpR2VEREfdBTW8SGDdYgU1Tu73h7a9fA==", + "dev": true, + "dependencies": { + "@vue/compiler-sfc": "^2.7.10", + "cac": "^6.7.12", + "chokidar": "^3.5.3", + "consola": "^2.15.3", + "detect-package-manager": "^2.0.1", + "magic-string": "^0.26.2", + "pathe": "^0.3.5", + "perfect-debounce": "^0.1.3", + "picocolors": "^1.0.0", + "postcss": "^8.4.16", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-load-config": "^4.0.1", + "postcss-logical": "^5.0.4", + "sass": "^1.54.5", + "unconfig": "^0.3.5", + "vite": "^3.0.9", + "vite-plugin-full-reload": "^1.0.4", + "vue": "^2.7.10" + }, + "bin": { + "kirbyup": "cli.mjs" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/magic-string": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.3.tgz", + "integrity": "sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.3.5.tgz", + "integrity": "sha512-grU/QeYP0ChuE5kjU2/k8VtAeODzbernHlue0gTa27+ayGIu3wqYBIPGfP9r5xSqgCgDd4nWrjKXEfxMillByg==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz", + "integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.10" + }, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + }, + "peerDependencies": { + "postcss": "^8.2" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "engines": { + "node": "^12 || ^14 || >=16" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/sass": { + "version": "1.54.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", + "integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unconfig": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.6.tgz", + "integrity": "sha512-JWefWyjLrDAbzs30sFkzcE9YpvAhN9+UPMZBwnNUmaY9X7QhI+wCGP4hoEWfZDzvkP+WIaZDPcMUJjarpxFvKg==", + "dev": true, + "dependencies": { + "@antfu/utils": "^0.5.2", + "defu": "^6.1.0", + "jiti": "^1.14.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", + "integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", + "dev": true, + "dependencies": { + "esbuild": "^0.15.6", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "less": "*", + "sass": "*", + "stylus": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz", + "integrity": "sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "vite": "^2 || ^3" + } + }, + "node_modules/vue": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz", + "integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==", + "dev": true, + "dependencies": { + "@vue/compiler-sfc": "2.7.10", + "csstype": "^3.1.0" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz", + "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@antfu/utils": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.5.2.tgz", + "integrity": "sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==", + "dev": true + }, + "@babel/parser": { + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.0.tgz", + "integrity": "sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==", + "dev": true + }, + "@esbuild/linux-loong64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.7.tgz", + "integrity": "sha512-IKznSJOsVUuyt7cDzzSZyqBEcZe+7WlBqTVXiF1OXP/4Nm387ToaXZ0fyLwI1iBlI/bzpxVq411QE2/Bt2XWWw==", + "dev": true, + "optional": true + }, + "@eslint/eslintrc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.1.tgz", + "integrity": "sha512-OhSY22oQQdw3zgPOOwdoj01l/Dzl1Z+xyUP33tkSN+aqyEhymJCcPHyXt+ylW8FSe0TfRC2VG+ROQOapD0aZSQ==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@humanwhocodes/config-array": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", + "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/gitignore-to-minimatch": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", + "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", + "dev": true + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@roomle/embedding-lib": { + "version": "4.42.0", + "resolved": "https://registry.npmjs.org/@roomle/embedding-lib/-/embedding-lib-4.42.0.tgz", + "integrity": "sha512-jnYIwJjKrqZwqLFzo6BgEmZ/r/cZ8XTKyqsU8tH3t1XuMI4IwfHn/RfoeBZPJv29pDuEc6clMNk9DWG3KE7GCg==", + "requires": { + "@roomle/web-sdk": "2.38.0" + } + }, + "@roomle/web-sdk": { + "version": "2.38.0", + "resolved": "https://registry.npmjs.org/@roomle/web-sdk/-/web-sdk-2.38.0.tgz", + "integrity": "sha512-1NQOjarzfq1DzmpidyjGRiMmJZSQ4UgHLQSSkVI8dierzx6A70NKo28+tHhFCuM1ZvVK3UeDhRIk02xC0NtoHw==" + }, + "@vue/compiler-sfc": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-2.7.10.tgz", + "integrity": "sha512-55Shns6WPxlYsz4WX7q9ZJBL77sKE1ZAYNYStLs6GbhIOMrNtjMvzcob6gu3cGlfpCR4bT7NXgyJ3tly2+Hx8Q==", + "dev": true, + "requires": { + "@babel/parser": "^7.18.4", + "postcss": "^8.4.14", + "source-map": "^0.6.1" + } + }, + "acorn": { + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", + "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "consola": { + "version": "2.15.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", + "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", + "integrity": "sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "defu": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.0.tgz", + "integrity": "sha512-pOFYRTIhoKujrmbTRhcW5lYQLBXw/dlTwfI8IguF1QCDJOcJzNH1w+YFjxqy6BAuJrClTy6MUE8q+oKJ2FLsIw==", + "dev": true + }, + "detect-package-manager": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/detect-package-manager/-/detect-package-manager-2.0.1.tgz", + "integrity": "sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==", + "dev": true, + "requires": { + "execa": "^5.1.1" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "esbuild": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.7.tgz", + "integrity": "sha512-7V8tzllIbAQV1M4QoE52ImKu8hT/NLGlGXkiDsbEU5PS6K8Mn09ZnYoS+dcmHxOS9CRsV4IRAMdT3I67IyUNXw==", + "dev": true, + "requires": { + "@esbuild/linux-loong64": "0.15.7", + "esbuild-android-64": "0.15.7", + "esbuild-android-arm64": "0.15.7", + "esbuild-darwin-64": "0.15.7", + "esbuild-darwin-arm64": "0.15.7", + "esbuild-freebsd-64": "0.15.7", + "esbuild-freebsd-arm64": "0.15.7", + "esbuild-linux-32": "0.15.7", + "esbuild-linux-64": "0.15.7", + "esbuild-linux-arm": "0.15.7", + "esbuild-linux-arm64": "0.15.7", + "esbuild-linux-mips64le": "0.15.7", + "esbuild-linux-ppc64le": "0.15.7", + "esbuild-linux-riscv64": "0.15.7", + "esbuild-linux-s390x": "0.15.7", + "esbuild-netbsd-64": "0.15.7", + "esbuild-openbsd-64": "0.15.7", + "esbuild-sunos-64": "0.15.7", + "esbuild-windows-32": "0.15.7", + "esbuild-windows-64": "0.15.7", + "esbuild-windows-arm64": "0.15.7" + } + }, + "esbuild-android-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.7.tgz", + "integrity": "sha512-p7rCvdsldhxQr3YHxptf1Jcd86dlhvc3EQmQJaZzzuAxefO9PvcI0GLOa5nCWem1AJ8iMRu9w0r5TG8pHmbi9w==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.7.tgz", + "integrity": "sha512-L775l9ynJT7rVqRM5vo+9w5g2ysbOCfsdLV4CWanTZ1k/9Jb3IYlQ06VCI1edhcosTYJRECQFJa3eAvkx72eyQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.7.tgz", + "integrity": "sha512-KGPt3r1c9ww009t2xLB6Vk0YyNOXh7hbjZ3EecHoVDxgtbUlYstMPDaReimKe6eOEfyY4hBEEeTvKwPsiH5WZg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.7.tgz", + "integrity": "sha512-kBIHvtVqbSGajN88lYMnR3aIleH3ABZLLFLxwL2stiuIGAjGlQW741NxVTpUHQXUmPzxi6POqc9npkXa8AcSZQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.7.tgz", + "integrity": "sha512-hESZB91qDLV5MEwNxzMxPfbjAhOmtfsr9Wnuci7pY6TtEh4UDuevmGmkUIjX/b+e/k4tcNBMf7SRQ2mdNuK/HQ==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.7.tgz", + "integrity": "sha512-dLFR0ChH5t+b3J8w0fVKGvtwSLWCv7GYT2Y2jFGulF1L5HftQLzVGN+6pi1SivuiVSmTh28FwUhi9PwQicXI6Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.7.tgz", + "integrity": "sha512-v3gT/LsONGUZcjbt2swrMjwxo32NJzk+7sAgtxhGx1+ZmOFaTRXBAi1PPfgpeo/J//Un2jIKm/I+qqeo4caJvg==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.7.tgz", + "integrity": "sha512-LxXEfLAKwOVmm1yecpMmWERBshl+Kv5YJ/1KnyAr6HRHFW8cxOEsEfisD3sVl/RvHyW//lhYUVSuy9jGEfIRAQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.7.tgz", + "integrity": "sha512-JKgAHtMR5f75wJTeuNQbyznZZa+pjiUHV7sRZp42UNdyXC6TiUYMW/8z8yIBAr2Fpad8hM1royZKQisqPABPvQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.7.tgz", + "integrity": "sha512-P3cfhudpzWDkglutWgXcT2S7Ft7o2e3YDMrP1n0z2dlbUZghUkKCyaWw0zhp4KxEEzt/E7lmrtRu/pGWnwb9vw==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.7.tgz", + "integrity": "sha512-T7XKuxl0VpeFLCJXub6U+iybiqh0kM/bWOTb4qcPyDDwNVhLUiPcGdG2/0S7F93czUZOKP57YiLV8YQewgLHKw==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.7.tgz", + "integrity": "sha512-6mGuC19WpFN7NYbecMIJjeQgvDb5aMuvyk0PDYBJrqAEMkTwg3Z98kEKuCm6THHRnrgsdr7bp4SruSAxEM4eJw==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.7.tgz", + "integrity": "sha512-uUJsezbswAYo/X7OU/P+PuL/EI9WzxsEQXDekfwpQ23uGiooxqoLFAPmXPcRAt941vjlY9jtITEEikWMBr+F/g==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.7.tgz", + "integrity": "sha512-+tO+xOyTNMc34rXlSxK7aCwJgvQyffqEM5MMdNDEeMU3ss0S6wKvbBOQfgd5jRPblfwJ6b+bKiz0g5nABpY0QQ==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.7.tgz", + "integrity": "sha512-yVc4Wz+Pu3cP5hzm5kIygNPrjar/v5WCSoRmIjCPWfBVJkZNb5brEGKUlf+0Y759D48BCWa0WHrWXaNy0DULTQ==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.7.tgz", + "integrity": "sha512-GsimbwC4FSR4lN3wf8XmTQ+r8/0YSQo21rWDL0XFFhLHKlzEA4SsT1Tl8bPYu00IU6UWSJ+b3fG/8SB69rcuEQ==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.7.tgz", + "integrity": "sha512-8CDI1aL/ts0mDGbWzjEOGKXnU7p3rDzggHSBtVryQzkSOsjCHRVe0iFYUuhczlxU1R3LN/E7HgUO4NXzGGP/Ag==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.7.tgz", + "integrity": "sha512-cOnKXUEPS8EGCzRSFa1x6NQjGhGsFlVgjhqGEbLTPsA7x4RRYiy2RKoArNUU4iR2vHmzqS5Gr84MEumO/wxYKA==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.7.tgz", + "integrity": "sha512-7MI08Ec2sTIDv+zH6StNBKO+2hGUYIT42GmFyW6MBBWWtJhTcQLinKS6ldIN1d52MXIbiJ6nXyCJ+LpL4jBm3Q==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.15.7", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.7.tgz", + "integrity": "sha512-R06nmqBlWjKHddhRJYlqDd3Fabx9LFdKcjoOy08YLimwmsswlFBJV4rXzZCxz/b7ZJXvrZgj8DDv1ewE9+StMw==", + "dev": true, + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.23.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.0.tgz", + "integrity": "sha512-pBG/XOn0MsJcKcTRLr27S5HpzQo4kLr+HjLQIyK4EiCsijDl/TB+h5uEuJU6bQ8Edvwz1XWOjpaP2qgnXGpTcA==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.3.1", + "@humanwhocodes/config-array": "^0.10.4", + "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@humanwhocodes/module-importer": "^1.0.1", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.15.0", + "globby": "^11.1.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "dev": true, + "requires": {} + }, + "eslint-plugin-vue": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.4.0.tgz", + "integrity": "sha512-Nzz2QIJ8FG+rtJaqT/7/ru5ie2XgT9KCudkbN0y3uFYhQ41nuHEaboLAiqwMcK006hZPQv/rVMRhUIwEGhIvfQ==", + "dev": true, + "requires": { + "eslint-utils": "^3.0.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.0.1", + "xml-name-validator": "^4.0.0" + } + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dev": true, + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "immutable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.1.0.tgz", + "integrity": "sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", + "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jiti": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.14.0.tgz", + "integrity": "sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "kirbyup": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kirbyup/-/kirbyup-2.0.1.tgz", + "integrity": "sha512-Yhjakz09gIqTUKZHK4EmU+sYK5H0aV//+WYThwzIWPTSzvS5iKT319wpR2VEREfdBTW8SGDdYgU1Tu73h7a9fA==", + "dev": true, + "requires": { + "@vue/compiler-sfc": "^2.7.10", + "cac": "^6.7.12", + "chokidar": "^3.5.3", + "consola": "^2.15.3", + "detect-package-manager": "^2.0.1", + "magic-string": "^0.26.2", + "pathe": "^0.3.5", + "perfect-debounce": "^0.1.3", + "picocolors": "^1.0.0", + "postcss": "^8.4.16", + "postcss-dir-pseudo-class": "^6.0.5", + "postcss-load-config": "^4.0.1", + "postcss-logical": "^5.0.4", + "sass": "^1.54.5", + "unconfig": "^0.3.5", + "vite": "^3.0.9", + "vite-plugin-full-reload": "^1.0.4", + "vue": "^2.7.10" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.3.tgz", + "integrity": "sha512-u1Po0NDyFcwdg2nzHT88wSK0+Rih0N1M+Ph1Sp08k8yvFFU3KR72wryS7e1qMPJypt99WB7fIFVCA92mQrMjrg==", + "dev": true, + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "pathe": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-0.3.5.tgz", + "integrity": "sha512-grU/QeYP0ChuE5kjU2/k8VtAeODzbernHlue0gTa27+ayGIu3wqYBIPGfP9r5xSqgCgDd4nWrjKXEfxMillByg==", + "dev": true + }, + "perfect-debounce": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-0.1.3.tgz", + "integrity": "sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-dir-pseudo-class": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.5.tgz", + "integrity": "sha512-eqn4m70P031PF7ZQIvSgy9RSJ5uI2171O/OO/zcRNYpJbvaeKFUlar1aJ7rmgiQtbm0FSPsRewjpdS0Oew7MPA==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.10" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-logical": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", + "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", + "dev": true, + "requires": {} + }, + "postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, + "resolve": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", + "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", + "dev": true, + "requires": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "2.78.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.78.1.tgz", + "integrity": "sha512-VeeCgtGi4P+o9hIg+xz4qQpRl6R401LWEXBmxYKOV4zlF82lyhgh2hTZnheFUbANE8l2A41F458iwj2vEYaXJg==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sass": { + "version": "1.54.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.54.8.tgz", + "integrity": "sha512-ib4JhLRRgbg6QVy6bsv5uJxnJMTS2soVcCp9Y88Extyy13A8vV0G1fAwujOzmNkFQbR3LvedudAMbtuNRPbQww==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "semver": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "unconfig": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/unconfig/-/unconfig-0.3.6.tgz", + "integrity": "sha512-JWefWyjLrDAbzs30sFkzcE9YpvAhN9+UPMZBwnNUmaY9X7QhI+wCGP4hoEWfZDzvkP+WIaZDPcMUJjarpxFvKg==", + "dev": true, + "requires": { + "@antfu/utils": "^0.5.2", + "defu": "^6.1.0", + "jiti": "^1.14.0" + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "vite": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-3.1.0.tgz", + "integrity": "sha512-YBg3dUicDpDWFCGttmvMbVyS9ydjntwEjwXRj2KBFwSB8SxmGcudo1yb8FW5+M/G86aS8x828ujnzUVdsLjs9g==", + "dev": true, + "requires": { + "esbuild": "^0.15.6", + "fsevents": "~2.3.2", + "postcss": "^8.4.16", + "resolve": "^1.22.1", + "rollup": "~2.78.0" + } + }, + "vite-plugin-full-reload": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.0.4.tgz", + "integrity": "sha512-9WejQII6zJ++m/YE173Zvl2jq4cqa404KNrVT+JDzDnqaGRq5UvOvA48fnsSWPIMXFV7S0dq5+sZqcSB+tKBgA==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "vue": { + "version": "2.7.10", + "resolved": "https://registry.npmjs.org/vue/-/vue-2.7.10.tgz", + "integrity": "sha512-HmFC70qarSHPXcKtW8U8fgIkF6JGvjEmDiVInTkKZP0gIlEPhlVlcJJLkdGIDiNkIeA2zJPQTWJUI4iWe+AVfg==", + "dev": true, + "requires": { + "@vue/compiler-sfc": "2.7.10", + "csstype": "^3.1.0" + } + }, + "vue-eslint-parser": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.0.3.tgz", + "integrity": "sha512-yL+ZDb+9T0ELG4VIFo/2anAOz8SvBdlqEnQnvJ3M7Scq56DvtjY0VY88bByRZB0D4J0u8olBcfrXTVONXsh4og==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.1.tgz", + "integrity": "sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3ae8b49 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "scripts": { + "build": "npm run build:panel && npm run build:public", + "build:panel": "kirbyup src/frontend/index.js", + "build:public": "esbuild src/frontend/public/*.js --outdir=assets --minify --bundle --splitting --format=esm --target=es6", + "dev:panel": "kirbyup src/frontend/index.js --watch", + "lint": "eslint --ext js,vue src/frontend", + "format": "prettier --write \"src/frontend/**/*.{js,vue}\"" + }, + "dependencies": { + "@roomle/embedding-lib": "^4.41.0" + }, + "devDependencies": { + "esbuild": "^0.15.6", + "eslint": "^8.16.0", + "eslint-config-prettier": "^8.5.0", + "eslint-plugin-vue": "^9.0.1", + "kirbyup": "^2.0.0", + "prettier": "^2.6.2" + }, + "browserslist": "> 2%" +} diff --git a/phpmd.xml.dist b/phpmd.xml.dist new file mode 100644 index 0000000..1f01864 --- /dev/null +++ b/phpmd.xml.dist @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..23ef219 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,24 @@ + + + + + + ./src/classes + + + + + + ./tests/ + + + diff --git a/psalm.xml.dist b/psalm.xml.dist new file mode 100644 index 0000000..296d7f3 --- /dev/null +++ b/psalm.xml.dist @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..a490511bc8f639175d0e26a9eb265b074c825b21 GIT binary patch literal 75959 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 + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Configuration extends Obj +{ + /** + * Identifier of the Roomle Catalog + * this configuration is part of + */ + public string $catalog; + + /** + * Deeplink to the configurator where + * this configuration was created + */ + public string $configuratorUrl; + + /** + * Depth of the configured product in mm + */ + public int $depth; + + /** + * Height of the configured product in mm + */ + public int $height; + + /** + * ID of the configuration + */ + public string $id; + + /** + * Product label + */ + public string $label; + + /** + * List of individual parts of the configuration + */ + public array $parts; + + /** + * Image URL of a perspective view of the configured product + */ + public string $perspectiveImage; + + /** + * Root component ID of the configuration + */ + public string $rootComponentId; + + /** + * Image URL of a top view of the configured product + */ + public string $topImage; + + /** + * Width of the configured product in mm + */ + public int $width; + + /** + * Constructor + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + * + * @throws \Kirby\Exception\InvalidArgumentException if no configuration data was passed as argument or in the request + * @throws \Kirby\Exception\InvalidArgumentException if the JSON data from the request could not be parsed + */ + public function __construct(array|string|null $data = null) + { + if ($data === null) { + $data = App::instance()->request()->get('roomle-configuration'); + + if (!$data) { + throw new InvalidArgumentException('No configuration data passed or available in request'); + } + } + + if (is_string($data) === true) { + $data = Json::decode($data); + } + + parent::__construct($data); + } + + /** + * Returns a plain text representation of the configuration + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/configuration', ['configuration' => $this], true); + } + + /** + * Returns the depth of the configured product + * as a human-readable string in cm + */ + public function depthLabel(): string + { + return static::formatLength($this->depth); + } + + /** + * Formats a length in millimeters as a + * human-readable string in cm + * @internal + */ + public static function formatLength(int $millimeters): string + { + $centimeters = $millimeters / 10; + + if (class_exists(NumberFormatter::class) === true) { + $formatter = new NumberFormatter(locale_get_default(), NumberFormatter::DECIMAL); + return $formatter->format($centimeters) . ' cm'; + } + + return $centimeters . ' cm'; // @codeCoverageIgnore + } + + /** + * Returns the height of the configured product + * as a human-readable string in cm + */ + public function heightLabel(): string + { + return static::formatLength($this->height); + } + + /** + * Creates an instance if data is available + * (either from the argument or the request) + * + * @param array|string|null $data `null` to get the data from the request (`roomle-configuration` param) + */ + public static function lazyInstance(array|string|null $data = null): self|null + { + try { + return new self($data); + } catch (InvalidArgumentException) { + return null; + } + } + + /** + * Returns the list of individual parts + * of the configuration as an object structure + * + * @throws \Kirby\Exception\InvalidArgumentException if a part in the raw data is not an array + */ + public function parts(): Collection + { + $parts = []; + foreach ($this->parts as $num => $part) { + if (is_array($part) !== true) { + throw new InvalidArgumentException('Invalid part ' . $num); + } + + $part = new Part($part); + + // double-check uninitialized property before access (normally should not happen); + // the Psalm error is suppressed because Psalm assumes all props to be + // initialized in the `Part` constructor (which `Obj` does not ensure) + /** @psalm-suppress TypeDoesNotContainType */ + if (isset($part->componentId) !== true) { + throw new InvalidArgumentException('Part ' . $num . ' does not have a component ID'); + } + + $parts[$part->componentId] = $part; + } + + return new Collection($parts); + } + + /** + * Returns the image of a perspective view of the configured product + */ + public function perspectiveImage(): Image + { + return new Image(['url' => $this->perspectiveImage]); + } + + /** + * Returns the image of a top view of the configured product + */ + public function topImage(): Image + { + return new Image(['url' => $this->topImage]); + } + + /** + * Returns the width of the configured product + * as a human-readable string in cm + */ + public function widthLabel(): string + { + return static::formatLength($this->width); + } +} diff --git a/src/classes/ConfiguratorBlock.php b/src/classes/ConfiguratorBlock.php new file mode 100644 index 0000000..1a5a5b1 --- /dev/null +++ b/src/classes/ConfiguratorBlock.php @@ -0,0 +1,202 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ +class ConfiguratorBlock extends Block +{ + /** + * Returns the Roomle configurator ID + * + * @throws \Kirby\Exception\InvalidArgumentException if no configurator ID was configured + */ + public function configuratorId(): string + { + $id = match ($this->content()->useConfiguratorId()->value()) { + 'default' => $this->option('configuratorId'), + 'custom' => $this->content()->configuratorId()->value(), + default => null + }; + + if (is_string($id) !== true || !$id) { + throw new InvalidArgumentException('Missing or invalid configurator ID setting'); + } + + return $id; + } + + /** + * Returns the data that is necessary for the JS logic + * as JSON string + */ + public function frontendJson(): string + { + return json_encode($this->frontendProps()); + } + + /** + * Returns the data that is necessary for the JS logic + */ + public function frontendProps(): array + { + return [ + 'configuratorId' => $this->configuratorId(), + 'htmlId' => $this->htmlId(), + 'options' => $this->options(), + 'targetUrl' => $this->targetUrl(), + ]; + } + + /** + * Returns whether variants have been configured + */ + public function hasVariants(): bool + { + return $this->variants()->isNotEmpty(); + } + + /** + * Returns the block ID used for matching the + * JS logic to the HTML code and query string + */ + public function htmlId(): string + { + return 'roomle-' . $this->id(); + } + + /** + * Returns the URL to the `configurator.js` file + */ + public function jsUrl(): string + { + /** @var \Kirby\Cms\Plugin $plugin */ + $plugin = $this->kirby()->plugin('lukasbestle/roomle'); + $mediaUrl = $plugin->mediaUrl(); + + return $mediaUrl . '/configurator.js'; + } + + /** + * Returns the Roomle item or configuration ID of the + * product to display by default + */ + public function mainProductId(): string + { + $id = $this->content()->mainProductId()->value(); + + if (!$id) { + throw new InvalidArgumentException('Missing main product ID'); + } + + return $id; + } + + /** + * Returns the custom options for the Roomle configurator + */ + public function options(): array + { + $defaults = []; + + // URL template for the configurator URL; + // the #CONFIGURATIONID# placeholder is dynamically replaced by Roomle + $defaults['deeplink'] = ( + $this->parent()->url() . + '?' . $this->htmlId() . '=#CONFIGURATIONID#' . + '#' . $this->htmlId() + ); + + // locale settings from the site's language code + $language = $this->kirby()->language(); + if ($language !== null) { + $code = $language->code(); + + if (Str::contains($code, '-') === true) { + $defaults['locale'] = Str::before($code, '-'); + $defaults['overrideCountry'] = Str::after($code, '-'); + } else { + $defaults['locale'] = $code; + } + } + + // no point displaying the "request product" button + // if no target page was configured + if ($this->targetUrl() === null) { + $defaults['buttons']['requestproduct'] = false; + } + + // assemble the options from the dynamic defaults with + // overrides from the config and the block settings + $overrides = $this->content()->options()->yaml(); + $options = array_merge($defaults, $this->option('options'), $overrides); + + // always set the `id` property from block data + $options['id'] = $this->mainProductId(); + + return $options; + } + + /** + * Returns the page to redirect to when + * "request product" is clicked + */ + public function targetUrl(): string|null + { + $defaultTarget = $this->option('target'); + if ($defaultTarget !== null) { + $defaultTarget = Url::to($defaultTarget); + } + + return match ($this->content()->useTarget()->value()) { + 'default' => $defaultTarget, + 'custom' => $this->content()->target()->toPage()?->url(), + default => null + }; + } + + /** + * Returns the variants the visitor can switch between + */ + public function variants(): Structure + { + $field = $this->content()->variants(); + $structure = new Structure([], $field->parent()); + + foreach ($field->value() as $id => $props) { + $variant = new ConfiguratorVariant([ + 'content' => $props, + 'id' => $props['id'] ?? $id, + 'parent' => $field->parent(), + 'structure' => $structure + ]); + + $structure->set($variant->id(), $variant); + } + + return $structure; + } + + /** + * Returns a configured plugin option value + */ + protected function option(string $option): mixed + { + return $this->kirby()->option('lukasbestle.roomle.' . $option); + } +} diff --git a/src/classes/ConfiguratorVariant.php b/src/classes/ConfiguratorVariant.php new file mode 100644 index 0000000..e59a87f --- /dev/null +++ b/src/classes/ConfiguratorVariant.php @@ -0,0 +1,43 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class ConfiguratorVariant extends StructureObject +{ + /** + * Returns an image object for the configured variant image + */ + public function image(): File|Image + { + $image = $this->content()->image()->toFile(); + if ($image !== null) { + return $image; + } + + // more than two colons = configuration, else item + $productId = $this->content()->productId()->value(); + if (count(explode(':', $productId)) > 2) { + $url = 'https://uploads.roomle.com/configurations/' . $productId . '/perspectiveImage.png'; + } else { + $url = 'https://www.roomle.com/api/v2/items/' . $productId . '/perspectiveImageHD'; + } + + return new Image(compact('url')); + } +} diff --git a/src/classes/Parameter.php b/src/classes/Parameter.php new file mode 100644 index 0000000..ec333ce --- /dev/null +++ b/src/classes/Parameter.php @@ -0,0 +1,107 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Parameter extends Obj +{ + /** + * Machine-readable key that + * identifies this parameter + */ + public string $key; + + /** + * Human-readable label that + * identifies this parameter + */ + public string|null $label; + + /** + * Data type of the `value` + * + * @var string 'Decimal'|'Integral'|'Material'|'String'|'Unknown' + */ + public string $type; + + /** + * Unit type for number values + * + * @var string|null 'angle'|'area'|'count'|'length'|null + */ + public string|null $unitType; + + /** + * Machine-readable value + */ + public string $value; + + /** + * Human-readable value + */ + public string|null $valueLabel; + + /** + * Returns a plain text representation of the parameter + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/parameter', ['parameter' => $this], true); + } + + /** + * Returns the human-readable label + * that identifies this parameter + * + * @return string + */ + public function label(): string + { + return $this->label ?? $this->key; + } + + /** + * Returns the machine-readable value + * depending on the `type` + */ + public function value(): string|float + { + if ($this->type === 'Decimal') { + return (float)$this->value; + } + + return $this->value; + } + + /** + * Returns the human-readable value + */ + public function valueLabel(): string + { + if ($this->type === 'Decimal' && $this->unitType === 'length') { + /** @var float $value */ + $value = $this->value(); + + return Configuration::formatLength((int)$value); + } + + return $this->valueLabel ?? $this->value; + } +} diff --git a/src/classes/Parameters.php b/src/classes/Parameters.php new file mode 100644 index 0000000..fcb6235 --- /dev/null +++ b/src/classes/Parameters.php @@ -0,0 +1,32 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Parameters extends Collection +{ + /** + * Returns a key-value array with the + * machine-readable parameter data + */ + public function rawData(): array + { + return array_map( + fn (Parameter $parameter): string|float => $parameter->value(), + $this->data + ); + } +} diff --git a/src/classes/Part.php b/src/classes/Part.php new file mode 100644 index 0000000..1f79dea --- /dev/null +++ b/src/classes/Part.php @@ -0,0 +1,112 @@ + + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + * + * @psalm-suppress PropertyNotSetInConstructor + */ +class Part extends Obj +{ + /** + * Article number of the part + */ + public string $articleNr; + + /** + * ID of the component + */ + public string $componentId; + + /** + * Number of units of this part + * in the configuration + */ + public int $count; + + /** + * Currency symbol for `price` and `retailerPrice` + */ + public string|null $currencySymbol; + + /** + * Human-readable part name + */ + public string $label; + + /** + * How many parts are contained + * in one item of the article number + */ + public int $packageSize; + + /** + * Configured parameters of this part + * + * @var array + */ + public array $parameters; + + /** + * Price of the part + */ + public float|null $price; + + /** + * Retailer price of the part + */ + public float|null $retailerPrice; + + /** + * Returns a plain text representation of the part + * e.g. for use in contact forms + */ + public function __toString(): string + { + // TODO: Remove suppression comment when support for Kirby 3.7.x is dropped + /** @psalm-suppress InternalMethod */ + return App::instance()->snippet('roomle/part', ['part' => $this], true); + } + + /** + * Returns the configured parameters + * of this part as an object structure + * + * @throws \Kirby\Exception\InvalidArgumentException if a parameter in the raw data is not an array + */ + public function parameters(): Parameters + { + $parameters = []; + foreach ($this->parameters as $num => $parameter) { + if (is_array($parameter) !== true) { + throw new InvalidArgumentException('Invalid parameter ' . $num); + } + + $parameter = new Parameter($parameter); + + // double-check uninitialized property before access (normally should not happen); + // the Psalm error is suppressed because Psalm assumes all props to be + // initialized in the `Parameter` constructor (which `Obj` does not ensure) + /** @psalm-suppress TypeDoesNotContainType */ + if (isset($parameter->key) !== true) { + throw new InvalidArgumentException('Parameter ' . $num . ' does not have a key'); + } + + $parameters[$parameter->key] = $parameter; + } + + return new Parameters($parameters); + } +} diff --git a/src/config/blockModels.php b/src/config/blockModels.php new file mode 100644 index 0000000..1b49eb0 --- /dev/null +++ b/src/config/blockModels.php @@ -0,0 +1,7 @@ + ConfiguratorBlock::class +]; diff --git a/src/config/blueprints.php b/src/config/blueprints.php new file mode 100644 index 0000000..c8e680b --- /dev/null +++ b/src/config/blueprints.php @@ -0,0 +1,5 @@ + __DIR__ . '/blueprints/blocks/roomle-configurator.yml' +]; diff --git a/src/config/blueprints/blocks/roomle-configurator.yml b/src/config/blueprints/blocks/roomle-configurator.yml new file mode 100644 index 0000000..be0c3ba --- /dev/null +++ b/src/config/blueprints/blocks/roomle-configurator.yml @@ -0,0 +1,162 @@ +name: + en: Roomle Configurator + de: Roomle-Konfigurator +icon: roomle +label: + en: "{{ mainproductid }} · {{ variants.length }} variant(s)" + de: "{{ mainproductid }} · {{ variants.length }} Variante(n)" +tabs: + products: + label: + en: Products + de: Produkte + fields: + mainProductId: + label: + en: Main product ID + de: Hauptprodukt-ID + type: text + counter: false + required: true + variants: + label: + en: Variants + de: Varianten + type: structure + columns: + productId: + mobile: true + title: + mobile: true + subtitle: + image: + empty: + en: No variants yet + de: Noch keine Varianten + fields: + productId: + label: + en: Product ID + de: Produkt-ID + type: text + counter: false + required: true + title: + label: + en: Title + de: Titel + type: text + required: true + subtitle: + label: + en: Subtitle + de: Untertitel + type: text + image: + label: + en: Image + de: Bild + type: files + empty: + en: No custom image yet, uses rendering of the product + de: Noch kein eigenes Bild, verwendet Rendering des Produkts + multiple: false + query: page.images + settings: + label: + en: Settings + de: Einstellungen + fields: + info: + label: false + type: info + text: + en: These settings override the global default settings. Only change them if needed. + de: Diese Einstellungen haben Vorrang vor den globalen Standard-Einstellungen. Verändere sie nur wenn nötig. + useConfiguratorId: + label: + en: Configurator ID + de: Konfigurator-ID + type: toggles + default: default + grow: false + options: + - + text: + en: Default + de: Standard + value: default + - + text: + en: Custom + de: Eigene + value: custom + required: true + width: 1/3 + gap: + type: gap + when: + useConfiguratorId: default + width: 2/3 + configuratorId: + label: + en: Custom configurator ID + de: Eigene Konfigurator-ID + type: text + counter: false + required: true + when: + useConfiguratorId: custom + width: 2/3 + useTarget: + label: + en: Request target page + de: Anfrage-Zielseite + type: toggles + default: default + grow: false + options: + - + text: + en: Default + de: Standard + value: default + - + text: + en: Custom + de: Eigene + value: custom + - + icon: cancel + text: + en: None + de: Keine + value: none + required: true + width: 1/3 + target: + label: + en: Custom target page + de: Eigene Zielseite + type: pages + empty: + en: No custom target page yet + de: Noch keine eigene Zielseite + multiple: false + required: true + when: + useTarget: custom + width: 2/3 + options: + label: + en: Configurator options + de: Konfigurator-Optionen + type: textarea + buttons: false + counter: false + font: monospace + help: + en: Overrides for the Roomle configurator options in YAML syntax + de: Ergänzende Roomle Konfigurator-Optionen in YAML-Syntax + size: medium + spellcheck: false diff --git a/src/config/i18n/de.php b/src/config/i18n/de.php new file mode 100644 index 0000000..2d578d1 --- /dev/null +++ b/src/config/i18n/de.php @@ -0,0 +1,6 @@ + 'Noch kein gültiges Hauptprodukt', + 'roomle.noRendering' => 'Kein Rendering verfügbar', +]; diff --git a/src/config/i18n/en.php b/src/config/i18n/en.php new file mode 100644 index 0000000..342d878 --- /dev/null +++ b/src/config/i18n/en.php @@ -0,0 +1,6 @@ + 'No valid main product yet', + 'roomle.noRendering' => 'No rendering available', +]; diff --git a/src/config/options.php b/src/config/options.php new file mode 100644 index 0000000..a4399b6 --- /dev/null +++ b/src/config/options.php @@ -0,0 +1,16 @@ + null, + + // page to redirect to when "request product" is clicked; + // defaults to none (request product feature is disabled) + 'target' => null, + + // custom options for the Roomle configurator; + // see https://docs.roomle.com/web/embedding/api/interfaces/types.UiInitData.html + 'options' => [], +]; diff --git a/src/config/snippets.php b/src/config/snippets.php new file mode 100644 index 0000000..d2c0e2e --- /dev/null +++ b/src/config/snippets.php @@ -0,0 +1,8 @@ + __DIR__ . '/snippets/blocks/roomle-configurator.php', + 'roomle/configuration' => __DIR__ . '/snippets/roomle/configuration.php', + 'roomle/parameter' => __DIR__ . '/snippets/roomle/parameter.php', + 'roomle/part' => __DIR__ . '/snippets/roomle/part.php' +]; diff --git a/src/config/snippets/blocks/roomle-configurator.php b/src/config/snippets/blocks/roomle-configurator.php new file mode 100644 index 0000000..42e0c10 --- /dev/null +++ b/src/config/snippets/blocks/roomle-configurator.php @@ -0,0 +1,38 @@ + +
+ hasVariants() === true): ?> + + + +
+ + +
diff --git a/src/config/snippets/roomle/configuration.php b/src/config/snippets/roomle/configuration.php new file mode 100644 index 0000000..b11fc9e --- /dev/null +++ b/src/config/snippets/roomle/configuration.php @@ -0,0 +1,8 @@ + +label() ?> (id() ?>) +W widthLabel() ?> / H heightLabel() ?> / D depthLabel() . "\n" ?> +configuratorUrl() . "\n" ?> + +parts() as $part): ?> + + diff --git a/src/config/snippets/roomle/parameter.php b/src/config/snippets/roomle/parameter.php new file mode 100644 index 0000000..cf1b856 --- /dev/null +++ b/src/config/snippets/roomle/parameter.php @@ -0,0 +1,2 @@ + +label() ?>: valueLabel() ?> diff --git a/src/config/snippets/roomle/part.php b/src/config/snippets/roomle/part.php new file mode 100644 index 0000000..9580512 --- /dev/null +++ b/src/config/snippets/roomle/part.php @@ -0,0 +1,5 @@ + +count() ?>x label() ?> (articleNr() ?>) +parameters() as $parameter): ?> + + diff --git a/src/config/translations.php b/src/config/translations.php new file mode 100644 index 0000000..e4686e3 --- /dev/null +++ b/src/config/translations.php @@ -0,0 +1,9 @@ + require __DIR__ . '/i18n/en.php', + + // additional translations + 'de' => require __DIR__ . '/i18n/de.php', +]; diff --git a/src/frontend/components/Configurator.vue b/src/frontend/components/Configurator.vue new file mode 100644 index 0000000..402cc0c --- /dev/null +++ b/src/frontend/components/Configurator.vue @@ -0,0 +1,133 @@ + + + + + diff --git a/src/frontend/components/LazyImage.vue b/src/frontend/components/LazyImage.vue new file mode 100644 index 0000000..acabccf --- /dev/null +++ b/src/frontend/components/LazyImage.vue @@ -0,0 +1,30 @@ + + + diff --git a/src/frontend/icons/roomle.svg b/src/frontend/icons/roomle.svg new file mode 100644 index 0000000..ee893c6 --- /dev/null +++ b/src/frontend/icons/roomle.svg @@ -0,0 +1,2 @@ + + diff --git a/src/frontend/index.js b/src/frontend/index.js new file mode 100644 index 0000000..e4fce17 --- /dev/null +++ b/src/frontend/index.js @@ -0,0 +1,15 @@ +import roomleIcon from "./icons/roomle.svg?raw"; +import Configurator from "./components/Configurator.vue"; +import LazyImage from "./components/LazyImage.vue"; + +panel.plugin("lukasbestle/roomle", { + blocks: { + "roomle-configurator": Configurator + }, + components: { + "lbro-lazy-image": LazyImage + }, + icons: { + roomle: roomleIcon + } +}); diff --git a/src/frontend/public/configurator.js b/src/frontend/public/configurator.js new file mode 100644 index 0000000..554a0ac --- /dev/null +++ b/src/frontend/public/configurator.js @@ -0,0 +1,213 @@ +import RoomleConfiguratorApi from "@roomle/embedding-lib/roomle-configurator-api.es"; + +/** + * Configurator + * + * @package Kirby Roomle Plugin + * @author Lukas Bestle + * @link https://github.com/lukasbestle/kirby-roomle + * @copyright Lukas Bestle + * @license https://opensource.org/licenses/MIT + */ +export default class { + /** + * @param {Object} props From `$block->frontendJson()` + */ + constructor(props) { + this.props = props; + this.variantSelector = + "#" + props.htmlId + " .roomle-configurator-variants input"; + + // override the item from the query param if set + this.currentId = this.idFromUrl() || props.options.id; + } + + /** + * Loads the Roomle configurator and initializes event listeners + */ + async init() { + // only initialize once + if (this.configurator) { + return; + } + + // handle selecting a different variant + document.querySelectorAll(this.variantSelector).forEach((input) => { + input.addEventListener("change", this.onVariantSelect.bind(this)); + }); + + // handle browser history changes + window.addEventListener("popstate", this.onUrlChange.bind(this)); + + // initialize the variant selector + this.updateVariants(this.currentId); + + // override the item in case an ID was extracted from the URL + const options = this.props.options; + options.id = this.currentId; + + // initialize the configured or passed product + this.configurator = await RoomleConfiguratorApi.createConfigurator( + this.props.configuratorId, + document.getElementById(this.props.htmlId + "-container"), + options + ); + + // update the configurator if the variant was changed during loading + if (this.currentId !== options.id) { + this.load(this.currentId); + } + + // handle the "request product" interaction + if (this.props.targetUrl) { + this.configurator.ui.callbacks.onRequestProduct = + this.onRequestProduct.bind(this); + } + } + + /** + * Extracts the item/configuration from the current URL + * + * @returns {String|null} + */ + idFromUrl() { + const params = new URLSearchParams(window.location.search); + return params.get(this.props.htmlId); + } + + /** + * Builds a URL for a specific item or configuration in this block + * + * @param {String} id Item or configuration ID + * @param {Boolean} hash Whether to include the hash of the current block + * @returns {URL} + */ + idToUrl(id, hash = false) { + const url = new URL(window.location.href); + url.searchParams.set(this.props.htmlId, id); + + if (hash === true) { + url.hash = this.props.htmlId; + } + + return url; + } + + /** + * Loads a new item or configuration into the configurator + * + * @param {String} id Item or configuration ID + * @returns {Boolean} Whether the configurator was updated + */ + async load(id) { + this.currentId = id; + this.updateVariants(id); + + if (!this.configurator) { + // not yet initialized + return false; + } + + await this.configurator.ui.loadObject(id); + + return true; + } + + /** + * Handles the user clicking the "request product" button + * in the configurator + * + * @param {String} configurationId ID of the current configuration + * @param {Base64Image} image Image of the current configuration + * @param {KernelPartList} partlist The part list with all details, grouped, etc. + * @param {Price} price Price of the current configuration, either set via setPrice or from Roomle price service + * @param {Labels} labels The label of the catalog and the furniture system + * @param {RapiConfigurationEnhanced} configuration The data returned from the Roomle backend + */ + onRequestProduct( + configurationId, + image, + partlist, + price, + labels, + configuration + ) { + const data = { + catalog: configuration.catalog, + configuratorUrl: this.idToUrl(configurationId, true), + depth: configuration.depth, + height: configuration.height, + id: configurationId, + label: configuration.label, + parts: partlist.fullList, + perspectiveImage: configuration.perspectiveImage, + rootComponentId: configuration.rootComponentId, + topImage: configuration.topImage, + width: configuration.width + }; + + // submit the data as a POST request to the target + const form = document.createElement("form"); + form.action = this.props.targetUrl; + form.method = "POST"; + form.style.display = "none"; + + const input = document.createElement("input"); + input.name = "roomle-configuration"; + input.value = JSON.stringify(data); + form.appendChild(input); + + document.body.appendChild(form); + form.submit(); + } + + /** + * Handles a browser history change (e.g. back button) + */ + onUrlChange() { + const id = this.idFromUrl(); + + if (id) { + this.load(id); + } else { + // reset to the original product + this.load(this.props.options.id); + } + } + + /** + * Handles the user selecting a different variant + * + * @param {Event} event Browser `change` event + */ + onVariantSelect(event) { + // update the page URL + const url = this.idToUrl(event.target.value); + window.history.pushState(null, "", url); + + this.load(event.target.value); + } + + /** + * Updates the variants UI with a new ID + * if a variant for that ID exists + * + * @param {String} id Item or configuration ID + */ + updateVariants(id) { + const oldInput = document.querySelector(this.variantSelector + "[checked]"); + const newInput = document.querySelector( + this.variantSelector + '[value="' + CSS.escape(id) + '"]' + ); + + if (oldInput) { + oldInput.removeAttribute("checked"); + oldInput.checked = false; + } + + if (newInput) { + newInput.checked = true; + newInput.setAttribute("checked", "true"); + } + } +} diff --git a/stubs/App.php b/stubs/App.php new file mode 100644 index 0000000..9d7a90d --- /dev/null +++ b/stubs/App.php @@ -0,0 +1,22 @@ + [ + 'body' => [ + 'roomle-configuration' => '{"depth": 123}' + ] + ] + ]); + + locale_set_default('en_US'); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_Array() + { + $configuration = new Configuration([ + 'depth' => 1337 + ]); + $this->assertSame(1337, $configuration->depth()); + + $configuration = Configuration::lazyInstance([ + 'depth' => 1337 + ]); + $this->assertSame(1337, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_Request() + { + $configuration = new Configuration(); + $this->assertSame(123, $configuration->depth()); + + $configuration = Configuration::lazyInstance(); + $this->assertSame(123, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_RequestInvalid() + { + // CMS instance without request data + new App(); + + $this->assertNull(Configuration::lazyInstance()); + + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('No configuration data passed or available in request'); + + new Configuration(); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_String() + { + $configuration = new Configuration('{"depth": 1337}'); + $this->assertSame(1337, $configuration->depth()); + + $configuration = Configuration::lazyInstance('{"depth": 1337}'); + $this->assertSame(1337, $configuration->depth()); + } + + /** + * @covers ::__construct + * @covers ::lazyInstance + */ + public function testConstruct_StringInvalid() + { + $this->assertNull(Configuration::lazyInstance('Definitely not JSON')); + + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('JSON string is invalid'); + + new Configuration('Definitely not JSON'); + } + + /** + * @covers ::formatLength + */ + public function testFormatLength() + { + $this->assertSame('0.1 cm', Configuration::formatLength(1)); + $this->assertSame('1 cm', Configuration::formatLength(10)); + $this->assertSame('1.3 cm', Configuration::formatLength(13)); + $this->assertSame('123.4 cm', Configuration::formatLength(1234)); + $this->assertSame('1,234.5 cm', Configuration::formatLength(12345)); + } + + /** + * @covers ::depthLabel + * @covers ::heightLabel + * @covers ::widthLabel + */ + public function testLengthLabels() + { + $configuration = new Configuration([ + 'depth' => 12345, + 'height' => 34567, + 'width' => 56789 + ]); + + $this->assertSame('1,234.5 cm', $configuration->depthLabel()); + $this->assertSame('3,456.7 cm', $configuration->heightLabel()); + $this->assertSame('5,678.9 cm', $configuration->widthLabel()); + } + + /** + * @covers ::parts + */ + public function testParts() + { + $configuration = new Configuration([ + 'parts' => [ + [ + 'componentId' => 'some:component1', + 'label' => 'Some component 1' + ], + [ + 'componentId' => 'some:component2', + 'label' => 'Some component 2' + ] + ] + ]); + + $parts = $configuration->parts(); + + $this->assertSame(2, $parts->count()); + $this->assertSame(['some:component1', 'some:component2'], $parts->keys()); + $this->assertInstanceOf(Part::class, $parts->first()); + $this->assertSame('Some component 1', $parts->first()->label()); + } + + /** + * @covers ::parts + */ + public function testParts_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Invalid part 0'); + + $configuration = new Configuration([ + 'parts' => [ + 'not an array' + ] + ]); + + $configuration->parts(); + } + + /** + * @covers ::parts + */ + public function testParts_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Part 0 does not have a component ID'); + + $configuration = new Configuration([ + 'parts' => [ + [ + 'label' => 'Some part without component ID' + ] + ] + ]); + + $configuration->parts(); + } + + /** + * @covers ::perspectiveImage + */ + public function testPerspectiveImage() + { + $configuration = new Configuration([ + 'perspectiveImage' => $url = 'https://example.com/path/to/image.jpg' + ]); + + $image = $configuration->perspectiveImage(); + $this->assertNull($image->root()); + $this->assertSame($url, $image->url()); + $this->assertSame('', $image->html()); + } + + /** + * @covers ::topImage + */ + public function testTopImage() + { + $configuration = new Configuration([ + 'topImage' => $url = 'https://example.com/path/to/image.jpg' + ]); + + $image = $configuration->topImage(); + $this->assertNull($image->root()); + $this->assertSame($url, $image->url()); + $this->assertSame('', $image->html()); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $configuration = new Configuration([ + 'configuratorUrl' => 'https://example.com/configurator', + 'depth' => 12, + 'height' => 34, + 'id' => 'some:id', + 'label' => 'Some product', + 'width' => 56, + 'parts' => [ + [ + 'articleNr' => '123.456.789', + 'componentId' => 'some:component1', + 'count' => 2, + 'label' => 'Some part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ], + [ + 'articleNr' => '987.654.321', + 'componentId' => 'some:component2', + 'count' => 1, + 'label' => 'Some other part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '789.0' + ] + ] + ] + ], + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/configuration.txt', (string)$configuration); + } +} diff --git a/tests/Roomle/ConfiguratorBlockTest.php b/tests/Roomle/ConfiguratorBlockTest.php new file mode 100644 index 0000000..980ce42 --- /dev/null +++ b/tests/Roomle/ConfiguratorBlockTest.php @@ -0,0 +1,770 @@ +app = new App([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'configuratorId' => 'defaultConfigurator', + 'target' => 'default-target', + 'options' => ['some' => 'default', 'other' => 'default'] + ] + ], + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'files' => [ + ['filename' => 'image.png'] + ] + ], + [ + 'slug' => 'custom-target' + ], + [ + 'slug' => 'default-target' + ] + ] + ], + 'urls' => [ + 'index' => 'https://example.com', + 'media' => 'https://media.example.com' + ] + ]); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Custom() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'custom', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('demoConfigurator', $block->configuratorId()); + } + + /** + * @covers ::configuratorId + * @covers ::option + */ + public function testConfiguratorId_Default() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'default', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('defaultConfigurator', $block->configuratorId()); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => '', + 'useconfiguratorid' => 'custom', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @covers ::configuratorId + * @covers ::option + */ + public function testConfiguratorId_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'configuratorId' => null + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'default', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @covers ::configuratorId + */ + public function testConfiguratorId_Invalid3() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing or invalid configurator ID setting'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => 'demoConfigurator', + 'useconfiguratorid' => 'invalid', + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->configuratorId(); + } + + /** + * @coversNothing + */ + public function testFields() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'configuratorid' => $configuratorId = 'demoConfigurator', + 'mainproductid' => $mainProductId = 'some:product', + 'options' => $options = 'some: option', + 'target' => $target = ['default-target'], + 'useconfiguratorid' => $useConfiguratorId = 'custom', + 'usetarget' => $useTarget = 'custom', + 'variants' => $variants = [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => $id = '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame($configuratorId, $block->content()->configuratorId()->value()); + $this->assertSame($mainProductId, $block->content()->mainProductId()->value()); + $this->assertSame($options, $block->content()->options()->value()); + $this->assertSame($target, $block->content()->target()->value()); + $this->assertSame($useConfiguratorId, $block->content()->useConfiguratorId()->value()); + $this->assertSame($useTarget, $block->content()->useTarget()->value()); + $this->assertSame($variants, $block->content()->variants()->value()); + + $this->assertSame($id, $block->id()); + } + + /** + * @covers ::frontendJson + */ + public function testFrontendJson() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'useconfiguratorid' => 'default', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame(json_encode([ + 'configuratorId' => 'defaultConfigurator', + 'htmlId' => 'roomle-12345678-90ab-cdef-1234-567890abcdef', + 'options' => [ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], + 'targetUrl' => 'https://example.com/default-target' + ]), $block->frontendJson()); + } + + /** + * @covers ::frontendProps + */ + public function testFrontendProps() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'useconfiguratorid' => 'default', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'configuratorId' => 'defaultConfigurator', + 'htmlId' => 'roomle-12345678-90ab-cdef-1234-567890abcdef', + 'options' => [ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], + 'targetUrl' => 'https://example.com/default-target' + ], $block->frontendProps()); + } + + /** + * @covers ::hasVariants + */ + public function testHasVariants_No() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertFalse($block->hasVariants()); + } + + /** + * @covers ::hasVariants + */ + public function testHasVariants_Yes() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertTrue($block->hasVariants()); + } + + /** + * @covers ::htmlId + */ + public function testHtmlId() + { + $block = new ConfiguratorBlock([ + 'content' => [], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('roomle-12345678-90ab-cdef-1234-567890abcdef', $block->htmlId()); + } + + /** + * @covers ::jsUrl + */ + public function testJsUrl() + { + $block = new ConfiguratorBlock([ + 'content' => [], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://media.example.com/plugins/lukasbestle/roomle/configurator.js', $block->jsUrl()); + } + + /** + * @covers ::mainProductId + */ + public function testMainProductId() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('some:product', $block->mainProductId()); + } + + /** + * @covers ::mainProductId + */ + public function testMainProductId_Invalid() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing main product ID'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => '' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->mainProductId(); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Defaults() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Locale1() + { + $app = $this->app->clone([ + 'languages' => [ + ['code' => 'en'], + ['code' => 'de'] + ] + ]); + $app->setCurrentLanguage('de'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/de/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'locale' => 'de', + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Locale2() + { + $app = $this->app->clone([ + 'languages' => [ + ['code' => 'en-us'], + ['code' => 'de-at'] + ] + ]); + $app->setCurrentLanguage('de-at'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/de-at/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'locale' => 'de', + 'overrideCountry' => 'at', + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_MissingProduct() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Missing main product ID'); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'options' => '', + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $block->options(); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_NoTarget() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => '', + 'usetarget' => 'none' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'buttons' => [ + 'requestproduct' => false + ], + 'some' => 'default', + 'other' => 'default', + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::options + * @covers ::option + */ + public function testOptions_Overrides() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'mainproductid' => 'some:product', + 'options' => "some: custom value\nskin:\n primary-color: '#123456'\nid: should not be used", + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame([ + 'deeplink' => ( + 'https://example.com/test' . + '?roomle-12345678-90ab-cdef-1234-567890abcdef=#CONFIGURATIONID#' . + '#roomle-12345678-90ab-cdef-1234-567890abcdef' + ), + 'some' => 'custom value', + 'other' => 'default', + 'skin' => [ + 'primary-color' => '#123456' + ], + 'id' => 'some:product', + ], $block->options()); + } + + /** + * @covers ::targetUrl + */ + public function testTargetUrl_Custom() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'custom' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/custom-target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + */ + public function testTargetUrl_CustomInvalid() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['does-not-exist'], + 'usetarget' => 'custom' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertNull($block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default1() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/default-target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default2() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => 'not-a-page' + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://example.com/not-a-page', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default3() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => 'https://external.com/target' + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertSame('https://external.com/target', $block->targetUrl()); + } + + /** + * @covers ::targetUrl + * @covers ::option + */ + public function testTargetUrl_Default4() + { + $app = $this->app->clone([ + 'options' => [ + 'lukasbestle.roomle' => [ + 'target' => null + ] + ] + ]); + + $block = new ConfiguratorBlock([ + 'content' => [ + 'target' => ['custom-target'], + 'usetarget' => 'default' + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $this->assertNull($block->targetUrl()); + } + + /** + * @covers ::Variants + */ + public function testVariants() + { + $block = new ConfiguratorBlock([ + 'content' => [ + 'variants' => [ + [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ], + [ + 'productid' => 'another:variant', + 'title' => 'Another variant', + 'subtitle' => 'for even cooler people', + 'image' => ['image.png'] + ] + ] + ], + 'id' => '12345678-90ab-cdef-1234-567890abcdef', + 'isHidden' => false, + 'parent' => $this->app->page('test'), + 'type' => 'roomle-configurator' + ]); + + $variants = $block->variants(); + + $this->assertSame(2, $variants->count()); + $this->assertInstanceOf(Page::class, $variants->parent()); + $this->assertSame($this->app->page('test'), $variants->parent()); + + $firstVariant = $variants->first(); + + $this->assertInstanceOf(ConfiguratorVariant::class, $firstVariant); + $this->assertSame('some:variant', $firstVariant->productId()->value()); + $this->assertInstanceOf(Page::class, $firstVariant->parent()); + $this->assertSame($this->app->page('test'), $firstVariant->parent()); + $this->assertInstanceOf(File::class, $firstVariant->image()); + $this->assertSame($this->app->file('test/image.png'), $firstVariant->image()); + } +} diff --git a/tests/Roomle/ConfiguratorVariantTest.php b/tests/Roomle/ConfiguratorVariantTest.php new file mode 100644 index 0000000..4494f7a --- /dev/null +++ b/tests/Roomle/ConfiguratorVariantTest.php @@ -0,0 +1,146 @@ +app = new App([ + 'site' => [ + 'children' => [ + [ + 'slug' => 'test', + 'files' => [ + ['filename' => 'image.png'] + ] + ] + ] + ] + ]); + } + + /** + * @coversNothing + */ + public function testConstruct() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => $productId = 'some:variant', + 'title' => $title = 'Some variant', + 'subtitle' => $subtitle = 'for cool people', + 'image' => $image = ['image.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $this->assertSame($productId, $variant->content()->productId()->value()); + $this->assertSame($title, $variant->content()->title()->value()); + $this->assertSame($subtitle, $variant->content()->subtitle()->value()); + $this->assertSame($image, $variant->content()->image()->value()); + + $this->assertSame($productId, $variant->productId()->value()); + $this->assertSame($title, $variant->title()->value()); + $this->assertSame($subtitle, $variant->subtitle()->value()); + } + + /** + * @covers ::image + */ + public function testImage_Configuration() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant:hash', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => [] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://uploads.roomle.com/configurations/some:variant:hash/perspectiveImage.png', $image->url()); + } + + /** + * @covers ::image + */ + public function testImage_File() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['image.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(File::class, $image); + $this->assertSame($this->app->file('test/image.png'), $image); + } + + /** + * @covers ::image + */ + public function testImage_FileInvalid() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => ['does-not-exist.png'] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://www.roomle.com/api/v2/items/some:variant/perspectiveImageHD', $image->url()); + } + + /** + * @covers ::image + */ + public function testImage_Item() + { + $variant = new ConfiguratorVariant([ + 'content' => [ + 'productid' => 'some:variant', + 'title' => 'Some variant', + 'subtitle' => 'for cool people', + 'image' => [] + ], + 'id' => 0, + 'parent' => $this->app->page('test') + ]); + + $image = $variant->image(); + + $this->assertInstanceOf(Image::class, $image); + $this->assertSame('https://www.roomle.com/api/v2/items/some:variant/perspectiveImageHD', $image->url()); + } +} diff --git a/tests/Roomle/ParameterTest.php b/tests/Roomle/ParameterTest.php new file mode 100644 index 0000000..a342f19 --- /dev/null +++ b/tests/Roomle/ParameterTest.php @@ -0,0 +1,133 @@ + $key = 'height', + 'type' => $type = 'Decimal', + 'unitType' => $unitType = 'length', + + // additional fields without documented properties + 'sort' => $sort = 0, + ]); + + $this->assertSame($key, $parameter->key()); + $this->assertSame($type, $parameter->type()); + $this->assertSame($unitType, $parameter->unitType()); + $this->assertSame($sort, $parameter->sort()); + } + + /** + * @covers ::label + */ + public function testLabel() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => 'Height' + ]); + + $this->assertSame('Height', $parameter->label()); + } + + /** + * @covers ::label + */ + public function testLabel_Fallback() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => null + ]); + + $this->assertSame('height', $parameter->label()); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $parameter = new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/parameter.txt', (string)$parameter); + } + + /** + * @covers ::value + */ + public function testValue() + { + $parameter = new Parameter([ + 'type' => 'String', + 'value' => '123.0' + ]); + + $this->assertSame('123.0', $parameter->value()); + } + + /** + * @covers ::value + */ + public function testValue_Decimal() + { + $parameter = new Parameter([ + 'type' => 'Decimal', + 'value' => '123.0' + ]); + + $this->assertSame(123.0, $parameter->value()); + } + + /** + * @covers ::valueLabel + */ + public function testValueLabel() + { + $parameter = new Parameter([ + 'type' => 'String', + 'value' => '123.0', + 'valueLabel' => 'Some string' + ]); + + $this->assertSame('Some string', $parameter->valueLabel()); + } + + /** + * @covers ::valueLabel + */ + public function testValueLabel_Length() + { + $parameter = new Parameter([ + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0', + 'valueLabel' => 'should not be used' + ]); + + $this->assertSame('12.3 cm', $parameter->valueLabel()); + } +} diff --git a/tests/Roomle/ParametersTest.php b/tests/Roomle/ParametersTest.php new file mode 100644 index 0000000..3674357 --- /dev/null +++ b/tests/Roomle/ParametersTest.php @@ -0,0 +1,67 @@ + new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]), + 'width' => new Parameter([ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ]) + ]); + + $this->assertSame(2, $parameters->count()); + $this->assertSame(['height', 'width'], $parameters->keys()); + $this->assertInstanceOf(Parameter::class, $parameters->first()); + $this->assertSame('12.3 cm', $parameters->first()->valueLabel()); + } + + /** + * @covers ::rawData + */ + public function testRawData() + { + $parameters = new Parameters([ + 'height' => new Parameter([ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ]), + 'width' => new Parameter([ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ]) + ]); + + $this->assertSame([ + 'height' => 123.0, + 'width' => 456.0 + ], $parameters->rawData()); + } +} diff --git a/tests/Roomle/PartTest.php b/tests/Roomle/PartTest.php new file mode 100644 index 0000000..7158eca --- /dev/null +++ b/tests/Roomle/PartTest.php @@ -0,0 +1,147 @@ + $articleNr = '123.456.789', + 'componentId' => $componentId = 'some:component', + 'count' => $count = 2, + 'currencySymbol' => $currencySymbol = '€', + 'label' => $label = 'Some component', + 'packageSize' => $packageSize = 1, + 'price' => $price = 123.45, + 'retailerPrice' => $retailerPrice = 234.56, + + // additional fields without documented properties + 'subpartId' => $subpartId = 12, + 'hasGeometry' => $hasGeometry = false, + ]); + + $this->assertSame($articleNr, $part->articleNr()); + $this->assertSame($componentId, $part->componentId()); + $this->assertSame($count, $part->count()); + $this->assertSame($currencySymbol, $part->currencySymbol()); + $this->assertSame($label, $part->label()); + $this->assertSame($packageSize, $part->packageSize()); + $this->assertSame($price, $part->price()); + $this->assertSame($retailerPrice, $part->retailerPrice()); + $this->assertSame($subpartId, $part->subpartId()); + $this->assertSame($hasGeometry, $part->hasGeometry()); + } + + /** + * @covers ::parameters + */ + public function testParameters() + { + $part = new Part([ + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ]); + + $parameters = $part->parameters(); + + $this->assertSame(2, $parameters->count()); + $this->assertSame(['height', 'width'], $parameters->keys()); + $this->assertInstanceOf(Parameter::class, $parameters->first()); + $this->assertSame('12.3 cm', $parameters->first()->valueLabel()); + } + + /** + * @covers ::parameters + */ + public function testParameters_Invalid1() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Invalid parameter 0'); + + $part = new Part([ + 'parameters' => [ + 'not an array' + ] + ]); + + $part->parameters(); + } + + /** + * @covers ::parameters + */ + public function testParameters_Invalid2() + { + $this->expectException('Kirby\Exception\InvalidArgumentException'); + $this->expectExceptionMessage('Parameter 0 does not have a key'); + + $part = new Part([ + 'parameters' => [ + [ + 'label' => 'Some parameter without key' + ] + ] + ]); + + $part->parameters(); + } + + /** + * @covers ::__toString + */ + public function testToString() + { + $part = new Part([ + 'articleNr' => '123.456.789', + 'componentId' => 'some:component1', + 'count' => 2, + 'label' => 'Some part', + 'parameters' => [ + [ + 'key' => 'height', + 'label' => 'Height', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '123.0' + ], + [ + 'key' => 'width', + 'label' => 'Width', + 'type' => 'Decimal', + 'unitType' => 'length', + 'value' => '456.0' + ] + ] + ]); + + $this->assertStringEqualsFile(__DIR__ . '/fixtures/part.txt', (string)$part); + } +} diff --git a/tests/Roomle/fixtures/configuration.txt b/tests/Roomle/fixtures/configuration.txt new file mode 100644 index 0000000..04606fa --- /dev/null +++ b/tests/Roomle/fixtures/configuration.txt @@ -0,0 +1,11 @@ +Some product (some:id) +W 5.6 cm / H 3.4 cm / D 1.2 cm +https://example.com/configurator + +2x Some part (123.456.789) +Height: 12.3 cm +Width: 45.6 cm + +1x Some other part (987.654.321) +Height: 78.9 cm + diff --git a/tests/Roomle/fixtures/parameter.txt b/tests/Roomle/fixtures/parameter.txt new file mode 100644 index 0000000..c9511aa --- /dev/null +++ b/tests/Roomle/fixtures/parameter.txt @@ -0,0 +1 @@ +Height: 12.3 cm \ No newline at end of file diff --git a/tests/Roomle/fixtures/part.txt b/tests/Roomle/fixtures/part.txt new file mode 100644 index 0000000..e2c3277 --- /dev/null +++ b/tests/Roomle/fixtures/part.txt @@ -0,0 +1,3 @@ +2x Some part (123.456.789) +Height: 12.3 cm +Width: 45.6 cm diff --git a/tests/bootstrap.php b/tests/bootstrap.php new file mode 100644 index 0000000..f9b4c49 --- /dev/null +++ b/tests/bootstrap.php @@ -0,0 +1,10 @@ +