diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0792692 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# Editor configuration, see https://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true + +[*.ts] +quote_type = single + +[*.md] +max_line_length = off +trim_trailing_whitespace = false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..75cc928 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,30 @@ +name: Build and Publish Web Site + +on: + push: + branches: + - master + - main + +jobs: + build: + name: Build and Push + runs-on: ubuntu-latest + + steps: + + - uses: actions/checkout@master + name: Checkout + + - name: Build + run: | + npm install + npm run build + - name: Redirect 404 to Index for SPA + run: cp dist/index.html dist/404.html + - name: Deploy + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./dist + cname: hub.angor.io diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..105c00f --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. + +# compiled output +/dist +/tmp +/out-tsc +# Only exists if Bazel was run +/bazel-out + +# dependencies +/node_modules + +# profiling files +chrome-profiler-events*.json + +# IDEs and editors +/.idea +.project +.classpath +.c9/ +*.launch +.settings/ +*.sublime-workspace + +# IDE - VSCode +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history/* + +# misc +/.angular/cache +/.sass-cache +/connect.lock +/coverage +/libpeerconnection.log +npm-debug.log +yarn-error.log +testem.log +/typings + +# System Files +.DS_Store +Thumbs.db diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..521a9f7 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +legacy-peer-deps=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..953c17e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "printWidth": 80, + "semi": true, + "bracketSameLine": false, + "trailingComma": "es5", + "tabWidth": 4, + "singleQuote": true, + "bracketSpacing": true, + "arrowParens": "always", + "plugins": ["prettier-plugin-organize-imports", "prettier-plugin-tailwindcss"] +} diff --git a/README.md b/README.md index e2c80cc..76ff685 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # Angor Hub -Angor Hub is a decentralized platform designed to connect investors with project founders. Leveraging the power of Nostr, it provides a secure and transparent environment for collaboration. The platform allows you to explore a wide variety of projects, engage with investors, and connect directly with founders. - -Whether you're an investor looking for the next big opportunity or a project founder seeking support, Angor Hub offers the tools you need to succeed. From secure messaging to group channels, Angor Hub ensures seamless interaction within a decentralized network. +Angor Hub is a Nostr client that is customized around the Angor protocol, a decentralized crowdfunding platform. Leveraging the power of Nostr the platform allows you to explore a wide variety of projects, engage with investors, and connect directly with founders. +Whether you're an investor looking for the next big opportunity or a project founder seeking funding, Angor Hub offers the tools you need to succeed. From project pages, secure messaging to group channels, Angor Hub ensures seamless interaction within a decentralized Nostr. diff --git a/angular.json b/angular.json new file mode 100644 index 0000000..1a001b8 --- /dev/null +++ b/angular.json @@ -0,0 +1,118 @@ +{ + "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "version": 1, + "newProjectRoot": "projects", + "projects": { + "angor": { + "projectType": "application", + "schematics": { + "@schematics/angular:component": { + "style": "scss" + } + }, + "root": "", + "sourceRoot": "src", + "prefix": "app", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:application", + "options": { + "outputPath": "dist", + "index": "src/index.html", + "browser": "src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "tsconfig.app.json", + "inlineStyleLanguage": "scss", + "allowedCommonJsDependencies": [ + "apexcharts", + "crypto-js/enc-utf8", + "crypto-js/hmac-sha256", + "crypto-js/enc-base64", + "quill-delta" + ], + "assets": [ + { + "glob": "**/*", + "input": "public" + }, + { + "glob": "_redirects", + "input": "src", + "output": "/" + } + ], + "stylePreprocessorOptions": { + "includePaths": ["src/@angor/styles"] + }, + "styles": [ + "src/@angor/styles/tailwind.scss", + "src/@angor/styles/themes.scss", + "src/styles/vendors.scss", + "src/@angor/styles/main.scss", + "src/styles/styles.scss", + "src/styles/tailwind.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "budgets": [ + { + "type": "initial", + "maximumWarning": "3mb", + "maximumError": "5mb" + }, + { + "type": "anyComponentStyle", + "maximumWarning": "75kb", + "maximumError": "90kb" + } + ], + "outputHashing": "all" + }, + "development": { + "optimization": false, + "extractLicenses": false, + "sourceMap": true + } + }, + "defaultConfiguration": "production" + }, + "serve": { + "builder": "@angular-devkit/build-angular:dev-server", + "configurations": { + "production": { + "buildTarget": "angor:build:production" + }, + "development": { + "buildTarget": "angor:build:development" + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n" + }, + "test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { + "polyfills": ["zone.js", "zone.js/testing"], + "tsConfig": "tsconfig.spec.json", + "inlineStyleLanguage": "scss", + "assets": [ + { + "glob": "**/*", + "input": "public" + } + ], + "styles": ["src/styles/styles.scss"], + "scripts": [] + } + } + } + } + }, + "cli": { + "analytics": "ca2ac0bc-0558-4b9d-b46e-815b90f23eb6" + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e8ab3d3 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,15006 @@ +{ + "name": "angor-browse", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "angor-browse", + "version": "0.0.1", + "license": "https://themeforest.net/licenses/standard", + "dependencies": { + "@angular/animations": "18.0.1", + "@angular/cdk": "18.0.1", + "@angular/common": "18.0.1", + "@angular/compiler": "18.0.1", + "@angular/core": "18.0.1", + "@angular/forms": "18.0.1", + "@angular/material": "18.0.1", + "@angular/material-luxon-adapter": "18.0.1", + "@angular/platform-browser": "18.0.1", + "@angular/platform-browser-dynamic": "18.0.1", + "@angular/router": "18.0.1", + "@gandlaf21/bolt11-decode": "^3.1.1", + "@ngneat/cashew": "^4.1.0", + "@ngneat/transloco": "6.0.4", + "@noble/hashes": "^1.5.0", + "@noble/secp256k1": "^2.1.0", + "apexcharts": "3.49.1", + "bech32": "^2.0.0", + "crypto-js": "4.2.0", + "dayjs": "^1.11.13", + "highlight.js": "11.9.0", + "lodash-es": "4.17.21", + "luxon": "3.4.4", + "ng-apexcharts": "1.10.0", + "ngx-indexed-db": "^19.0.0", + "ngx-quill": "26.0.1", + "nostr-tools": "^2.7.2", + "perfect-scrollbar": "1.5.5", + "quill": "2.0.2", + "rxjs": "7.8.1", + "tslib": "2.6.2", + "zone.js": "0.14.6" + }, + "devDependencies": { + "@angular-devkit/build-angular": "18.0.2", + "@angular/cli": "18.0.2", + "@angular/compiler-cli": "18.0.1", + "@tailwindcss/typography": "0.5.13", + "@types/chroma-js": "2.4.4", + "@types/crypto-js": "4.2.2", + "@types/highlight.js": "10.1.0", + "@types/jasmine": "5.1.4", + "@types/lodash": "4.17.4", + "@types/lodash-es": "4.17.12", + "@types/luxon": "3.4.2", + "autoprefixer": "10.4.19", + "chroma-js": "2.4.2", + "jasmine-core": "5.1.2", + "karma": "6.4.3", + "karma-chrome-launcher": "3.2.0", + "karma-coverage": "2.2.1", + "karma-jasmine": "5.1.0", + "karma-jasmine-html-reporter": "2.1.0", + "lodash": "4.17.21", + "postcss": "8.4.38", + "prettier": "3.3.0", + "prettier-plugin-organize-imports": "3.2.4", + "prettier-plugin-tailwindcss": "0.6.1", + "tailwindcss": "3.4.3", + "typescript": "5.4.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@angular-devkit/architect": { + "version": "0.1800.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1800.2.tgz", + "integrity": "sha512-PX7lCTAqWe9C40+fie+DAc8vhpGA+JgZKWWrMHUTV/iZx8RXx2X4xGQsqYu36p4i3MSfQdbn+0xLWGmjScPVOQ==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular-devkit/build-angular": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-18.0.2.tgz", + "integrity": "sha512-cQkTx7XaIPj6+DXo6wZmO4iY0hOOfPDnSN/+m84XpBW0tuPGxH7Z9B6wV+Uwcpm9HGPqzRA7VZyPsqbK860b0Q==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1800.2", + "@angular-devkit/build-webpack": "0.1800.2", + "@angular-devkit/core": "18.0.2", + "@angular/build": "18.0.2", + "@babel/core": "7.24.5", + "@babel/generator": "7.24.5", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.24.5", + "@babel/plugin-transform-async-generator-functions": "7.24.3", + "@babel/plugin-transform-async-to-generator": "7.24.1", + "@babel/plugin-transform-runtime": "7.24.3", + "@babel/preset-env": "7.24.5", + "@babel/runtime": "7.24.5", + "@discoveryjs/json-ext": "0.5.7", + "@ngtools/webpack": "18.0.2", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "autoprefixer": "10.4.19", + "babel-loader": "9.1.3", + "babel-plugin-istanbul": "6.1.1", + "browserslist": "^4.21.5", + "copy-webpack-plugin": "11.0.0", + "critters": "0.0.22", + "css-loader": "7.1.1", + "esbuild-wasm": "0.21.3", + "fast-glob": "3.3.2", + "http-proxy-middleware": "3.0.0", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.22", + "jsonc-parser": "3.2.1", + "karma-source-map-support": "1.4.0", + "less": "4.2.0", + "less-loader": "12.2.0", + "license-webpack-plugin": "4.0.2", + "loader-utils": "3.2.1", + "magic-string": "0.30.10", + "mini-css-extract-plugin": "2.9.0", + "mrmime": "2.0.0", + "open": "8.4.2", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.5.0", + "postcss": "8.4.38", + "postcss-loader": "8.1.1", + "resolve-url-loader": "5.0.0", + "rxjs": "7.8.1", + "sass": "1.77.2", + "sass-loader": "14.2.1", + "semver": "7.6.2", + "source-map-loader": "5.0.0", + "source-map-support": "0.5.21", + "terser": "5.31.0", + "tree-kill": "1.2.2", + "tslib": "2.6.2", + "undici": "6.18.0", + "vite": "5.2.11", + "watchpack": "2.4.1", + "webpack": "5.91.0", + "webpack-dev-middleware": "7.2.1", + "webpack-dev-server": "5.0.4", + "webpack-merge": "5.10.0", + "webpack-subresource-integrity": "5.1.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "optionalDependencies": { + "esbuild": "0.21.3" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "@web/test-runner": "^0.18.0", + "browser-sync": "^3.0.2", + "jest": "^29.5.0", + "jest-environment-jsdom": "^29.5.0", + "karma": "^6.3.0", + "ng-packagr": "^18.0.0", + "protractor": "^7.0.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.5" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "@web/test-runner": { + "optional": true + }, + "browser-sync": { + "optional": true + }, + "jest": { + "optional": true + }, + "jest-environment-jsdom": { + "optional": true + }, + "karma": { + "optional": true + }, + "ng-packagr": { + "optional": true + }, + "protractor": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular-devkit/build-angular/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular-devkit/build-angular/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular-devkit/build-webpack": { + "version": "0.1800.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1800.2.tgz", + "integrity": "sha512-CbTURBhZWzx+5KewS2Nkqy2rwBTFgDCvUwONGWuy1K68+85vOWUKqjkfvriHA+JkWN03w7FzWEtTfcOg0EzYkw==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1800.2", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "webpack": "^5.30.0", + "webpack-dev-server": "^5.0.2" + } + }, + "node_modules/@angular-devkit/core": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.0.2.tgz", + "integrity": "sha512-QXcEdfmODc0rKblBerk30yw70fypIkFm6gQBLJgsshpwc+TMA+fuMLcPQebOTzKLtD2tNUkk/7SrWPQIGqeXaA==", + "dev": true, + "dependencies": { + "ajv": "8.13.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.2.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/core/node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.0.2.tgz", + "integrity": "sha512-G9yGcoB67sH0eRNWoiQWNn2KwiI7sDasVscYPGKf1yo7JRiXmzX/LpfKRPsZTl+Bs0FItnwDInsqgMisK89/6g==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.2", + "jsonc-parser": "3.2.1", + "magic-string": "0.30.10", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/animations": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.0.1.tgz", + "integrity": "sha512-QAY/oxfuFY2Bjr3foniWlLAiddXHu8879lZvXHt1NVOsiav+vD15IEEQsnuQbJPy/EHEnAlUh9UptB4zQIBp/Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.1" + } + }, + "node_modules/@angular/build": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@angular/build/-/build-18.0.2.tgz", + "integrity": "sha512-iPPHdAJ3LiR8t/+39xjvrqMWcTmRrfphzKxXoIVDcswQjVQIk00EYuxinC6EVa7dSKDl1thk1MeCNZ9DIjaAvQ==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "2.3.0", + "@angular-devkit/architect": "0.1800.2", + "@babel/core": "7.24.5", + "@babel/helper-annotate-as-pure": "7.22.5", + "@babel/helper-split-export-declaration": "7.24.5", + "@vitejs/plugin-basic-ssl": "1.1.0", + "ansi-colors": "4.1.3", + "browserslist": "^4.23.0", + "critters": "0.0.22", + "esbuild": "0.21.3", + "fast-glob": "3.3.2", + "https-proxy-agent": "7.0.4", + "inquirer": "9.2.22", + "lmdb": "3.0.8", + "magic-string": "0.30.10", + "mrmime": "2.0.0", + "ora": "5.4.1", + "parse5-html-rewriting-stream": "7.0.0", + "picomatch": "4.0.2", + "piscina": "4.5.0", + "sass": "1.77.2", + "semver": "7.6.2", + "undici": "6.18.0", + "vite": "5.2.11", + "watchpack": "2.4.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "@angular/localize": "^18.0.0", + "@angular/platform-server": "^18.0.0", + "@angular/service-worker": "^18.0.0", + "less": "^4.2.0", + "postcss": "^8.4.0", + "tailwindcss": "^2.0.0 || ^3.0.0", + "typescript": ">=5.4 <5.5" + }, + "peerDependenciesMeta": { + "@angular/localize": { + "optional": true + }, + "@angular/platform-server": { + "optional": true + }, + "@angular/service-worker": { + "optional": true + }, + "less": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tailwindcss": { + "optional": true + } + } + }, + "node_modules/@angular/build/node_modules/@babel/core": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", + "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.24.5", + "@babel/helpers": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@angular/build/node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@angular/build/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@angular/build/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/cdk": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.0.1.tgz", + "integrity": "sha512-2fCqX1sz5cM+LncO6ak4EU2ZBm8MWitv5V53go3Iz5dOVOdrvysBt8smEkWZ4nvEKkFYHEPpQo0YlxEWbuTEmA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "optionalDependencies": { + "parse5": "^7.1.2" + }, + "peerDependencies": { + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/cli": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-18.0.2.tgz", + "integrity": "sha512-shrxMD1bcWWh7WpBN3KTV+Lt8E62gURSUFhs6kdGLepMDif8LPAv45+hpt8SBU9VfQuL6AHa4cW8uDL9BKGlYA==", + "dev": true, + "dependencies": { + "@angular-devkit/architect": "0.1800.2", + "@angular-devkit/core": "18.0.2", + "@angular-devkit/schematics": "18.0.2", + "@schematics/angular": "18.0.2", + "@yarnpkg/lockfile": "1.1.0", + "ansi-colors": "4.1.3", + "ini": "4.1.2", + "inquirer": "9.2.22", + "jsonc-parser": "3.2.1", + "npm-package-arg": "11.0.2", + "npm-pick-manifest": "9.0.1", + "ora": "5.4.1", + "pacote": "18.0.6", + "resolve": "1.22.8", + "semver": "7.6.2", + "symbol-observable": "4.0.0", + "yargs": "17.7.2" + }, + "bin": { + "ng": "bin/ng.js" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@angular/common": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-18.0.1.tgz", + "integrity": "sha512-iADQC5m4fvk+VNXEoU1KR93b0eG218/GuNdzUNVJHcjxdFxPshKk5fiaGSosUCxXPRQOxDKzmS9EDang87E/Ew==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/compiler": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-18.0.1.tgz", + "integrity": "sha512-zyG/ifCtN0drAuwz0oV6LtzTiDREsM1Ay7eJW9wTvp3NCv06goHLtHXX12eFfZQWJViBv924lyRDSWdZN7r3GQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/core": "18.0.1" + }, + "peerDependenciesMeta": { + "@angular/core": { + "optional": true + } + } + }, + "node_modules/@angular/compiler-cli": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-18.0.1.tgz", + "integrity": "sha512-Aoz70+/o8R2lG2EGDAYbj6yu2B7kqa/9loYEwG0fECJTtXoRBP+bEGpUxMmxOb59tMDnbIhBHmNPPEQVTXvgSQ==", + "dev": true, + "dependencies": { + "@babel/core": "7.24.4", + "@jridgewell/sourcemap-codec": "^1.4.14", + "chokidar": "^3.0.0", + "convert-source-map": "^1.5.1", + "reflect-metadata": "^0.2.0", + "semver": "^7.0.0", + "tslib": "^2.3.0", + "yargs": "^17.2.1" + }, + "bin": { + "ng-xi18n": "bundles/src/bin/ng_xi18n.js", + "ngc": "bundles/src/bin/ngc.js", + "ngcc": "bundles/ngcc/index.js" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/compiler": "18.0.1", + "typescript": ">=5.4 <5.5" + } + }, + "node_modules/@angular/core": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-18.0.1.tgz", + "integrity": "sha512-Db1livvugoLdLsWww5IqUS5v+yUN7/5Rj0trZv9BgxIuoNtoipfLqKHwZWpumH3yI5Ucu+UH9zZ1mlGyF0Kexw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "rxjs": "^6.5.3 || ^7.4.0", + "zone.js": "~0.14.0" + } + }, + "node_modules/@angular/forms": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.0.1.tgz", + "integrity": "sha512-j1nUzwnZHO/BRXK0joQbAV10JWxeRVKmPzIaDulY2o28Er1jVKyw2T8EwI+xSvBbAqyJyaAd+ysWUhm3FfH+GA==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.1", + "@angular/core": "18.0.1", + "@angular/platform-browser": "18.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-18.0.1.tgz", + "integrity": "sha512-y8OaESXw32P74Jh2FEr3n7QjqjTlo2Jf+XdgOvp5dd1yxpJ20vnK7ZCEQqCpxdxGAzXqR+2DccKk9tebB9egZw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^18.0.0 || ^19.0.0", + "@angular/cdk": "18.0.1", + "@angular/common": "^18.0.0 || ^19.0.0", + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/forms": "^18.0.0 || ^19.0.0", + "@angular/platform-browser": "^18.0.0 || ^19.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material-luxon-adapter": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/material-luxon-adapter/-/material-luxon-adapter-18.0.1.tgz", + "integrity": "sha512-gmcn95t4rIQIXdpcmjXr1AL/Wp/m0+A+SAZ/sYduo7iCBvQX9VO4se70xx1PULgAvS1AmijEUmAW5tDwxzTJvQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0 || ^19.0.0", + "@angular/material": "18.0.1", + "luxon": "^3.0.0" + } + }, + "node_modules/@angular/platform-browser": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.0.1.tgz", + "integrity": "sha512-rQUsOxZxiwSPvyHdne60IKIGsvFoVc1rO4mDyXU+9sCCLmPKHzNyEzp7vybTZeiqa3k6v3sV/bfHWwrRzmvenw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/animations": "18.0.1", + "@angular/common": "18.0.1", + "@angular/core": "18.0.1" + }, + "peerDependenciesMeta": { + "@angular/animations": { + "optional": true + } + } + }, + "node_modules/@angular/platform-browser-dynamic": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-18.0.1.tgz", + "integrity": "sha512-lzjq7HjigGxO5oh5Sw0Vxa3mAVidYHpHFQr46/OSl9T5jLpStcjEqK0xcfQz9bf2hV+0qFfMqmd2k0XQl7feqg==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.1", + "@angular/compiler": "18.0.1", + "@angular/core": "18.0.1", + "@angular/platform-browser": "18.0.1" + } + }, + "node_modules/@angular/router": { + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-18.0.1.tgz", + "integrity": "sha512-PapdvfATjRZI0cJ/RH8n/ixHDHa4HIBaOMwhgU73InU9t6NIhBXg6aRECYV2qGt7NtpLYSHmG5Z1Ws86rm5Tyw==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.13.0 || >=20.9.0" + }, + "peerDependencies": { + "@angular/common": "18.0.1", + "@angular/core": "18.0.1", + "@angular/platform-browser": "18.0.1", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", + "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "dependencies": { + "@babel/highlight": "^7.24.2", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", + "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.4.tgz", + "integrity": "sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.24.4", + "@babel/parser": "^7.24.4", + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.1", + "@babel/types": "^7.24.0", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", + "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", + "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", + "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-member-expression-to-functions": "^7.24.5", + "@babel/helper-optimise-call-expression": "^7.22.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", + "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", + "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", + "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", + "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-simple-access": "^7.24.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", + "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", + "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", + "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-wrap-function": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", + "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-member-expression-to-functions": "^7.23.0", + "@babel/helper-optimise-call-expression": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", + "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", + "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", + "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", + "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", + "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", + "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "dev": true, + "dependencies": { + "@babel/helper-function-name": "^7.23.0", + "@babel/template": "^7.24.0", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", + "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.24.0", + "@babel/traverse": "^7.24.5", + "@babel/types": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", + "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.5", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", + "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", + "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", + "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-transform-optional-chaining": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", + "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", + "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", + "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", + "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", + "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/plugin-syntax-async-generators": "^7.8.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", + "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-remap-async-to-generator": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", + "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", + "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", + "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", + "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.4", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", + "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-replace-supers": "^7.24.1", + "@babel/helper-split-export-declaration": "^7.24.5", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", + "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/template": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", + "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", + "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", + "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", + "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", + "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "dev": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", + "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", + "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", + "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", + "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", + "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", + "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", + "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", + "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", + "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-simple-access": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", + "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "dev": true, + "dependencies": { + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", + "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "dev": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", + "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.5", + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", + "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", + "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", + "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", + "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "dev": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", + "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-replace-supers": "^7.24.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", + "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", + "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", + "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", + "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.1", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", + "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-create-class-features-plugin": "^7.24.5", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", + "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", + "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", + "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", + "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.3", + "@babel/helper-plugin-utils": "^7.24.0", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", + "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", + "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", + "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", + "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", + "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", + "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", + "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", + "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", + "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "dev": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.22.15", + "@babel/helper-plugin-utils": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", + "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.24.4", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-validator-option": "^7.23.5", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.1", + "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.1", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.24.1", + "@babel/plugin-transform-block-scoped-functions": "^7.24.1", + "@babel/plugin-transform-block-scoping": "^7.24.5", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-class-static-block": "^7.24.4", + "@babel/plugin-transform-classes": "^7.24.5", + "@babel/plugin-transform-computed-properties": "^7.24.1", + "@babel/plugin-transform-destructuring": "^7.24.5", + "@babel/plugin-transform-dotall-regex": "^7.24.1", + "@babel/plugin-transform-duplicate-keys": "^7.24.1", + "@babel/plugin-transform-dynamic-import": "^7.24.1", + "@babel/plugin-transform-exponentiation-operator": "^7.24.1", + "@babel/plugin-transform-export-namespace-from": "^7.24.1", + "@babel/plugin-transform-for-of": "^7.24.1", + "@babel/plugin-transform-function-name": "^7.24.1", + "@babel/plugin-transform-json-strings": "^7.24.1", + "@babel/plugin-transform-literals": "^7.24.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-member-expression-literals": "^7.24.1", + "@babel/plugin-transform-modules-amd": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.24.1", + "@babel/plugin-transform-modules-systemjs": "^7.24.1", + "@babel/plugin-transform-modules-umd": "^7.24.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", + "@babel/plugin-transform-new-target": "^7.24.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-object-super": "^7.24.1", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.24.5", + "@babel/plugin-transform-private-methods": "^7.24.1", + "@babel/plugin-transform-private-property-in-object": "^7.24.5", + "@babel/plugin-transform-property-literals": "^7.24.1", + "@babel/plugin-transform-regenerator": "^7.24.1", + "@babel/plugin-transform-reserved-words": "^7.24.1", + "@babel/plugin-transform-shorthand-properties": "^7.24.1", + "@babel/plugin-transform-spread": "^7.24.1", + "@babel/plugin-transform-sticky-regex": "^7.24.1", + "@babel/plugin-transform-template-literals": "^7.24.1", + "@babel/plugin-transform-typeof-symbol": "^7.24.5", + "@babel/plugin-transform-unicode-escapes": "^7.24.1", + "@babel/plugin-transform-unicode-property-regex": "^7.24.1", + "@babel/plugin-transform-unicode-regex": "^7.24.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.31.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true + }, + "node_modules/@babel/runtime": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", + "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "dev": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.24.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", + "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.24.0", + "@babel/types": "^7.24.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", + "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.2", + "@babel/generator": "^7.24.5", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.24.5", + "@babel/parser": "^7.24.5", + "@babel/types": "^7.24.5", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", + "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.1", + "@babel/helper-validator-identifier": "^7.24.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.3.tgz", + "integrity": "sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.3.tgz", + "integrity": "sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.3.tgz", + "integrity": "sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.3.tgz", + "integrity": "sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.3.tgz", + "integrity": "sha512-U3fuQ0xNiAkXOmQ6w5dKpEvXQRSpHOnbw7gEfHCRXPeTKW9sBzVck6C5Yneb8LfJm0l6le4NQfkNPnWMSlTFUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.3.tgz", + "integrity": "sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.3.tgz", + "integrity": "sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.3.tgz", + "integrity": "sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.3.tgz", + "integrity": "sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.3.tgz", + "integrity": "sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.3.tgz", + "integrity": "sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.3.tgz", + "integrity": "sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.3.tgz", + "integrity": "sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.3.tgz", + "integrity": "sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.3.tgz", + "integrity": "sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.3.tgz", + "integrity": "sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.3.tgz", + "integrity": "sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.3.tgz", + "integrity": "sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.3.tgz", + "integrity": "sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.3.tgz", + "integrity": "sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.3.tgz", + "integrity": "sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.3.tgz", + "integrity": "sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.3.tgz", + "integrity": "sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@gandlaf21/bolt11-decode": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@gandlaf21/bolt11-decode/-/bolt11-decode-3.1.1.tgz", + "integrity": "sha512-uorLVc3FAWO6USCaBHGixwUd7B86z4IVRa/TdLicsWi3Vm7QngYDIuUkOBemut0Qh/Qtsx47EjWMXS4WHel98A==", + "license": "MIT", + "dependencies": { + "bech32": "^1.1.2", + "bn.js": "^4.11.8", + "buffer": "^6.0.3" + } + }, + "node_modules/@gandlaf21/bolt11-decode/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT" + }, + "node_modules/@gandlaf21/bolt11-decode/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.2.tgz", + "integrity": "sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jsonjoy.com/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/json-pack": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz", + "integrity": "sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg==", + "dev": true, + "dependencies": { + "@jsonjoy.com/base64": "^1.1.1", + "@jsonjoy.com/util": "^1.1.2", + "hyperdyperid": "^1.2.0", + "thingies": "^1.20.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@jsonjoy.com/util": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.1.3.tgz", + "integrity": "sha512-g//kkF4kOwUjemValCtOc/xiYzmwMRmWq3Bn+YnzOzuZLHq2PpMOxxIayN3cKbo7Ko2Np65t6D9H81IvXbXhqg==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/@leichtgewicht/ip-codec": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", + "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", + "dev": true + }, + "node_modules/@ljharb/through": { + "version": "2.3.13", + "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", + "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.0.8.tgz", + "integrity": "sha512-+lFwFvU+zQ9zVIFETNtmW++syh3Ps5JS8MPQ8zOYtQZoU+dTR8ivWHTaE2QVk1JG2payGDLUAvpndLAjGMdeeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.0.8.tgz", + "integrity": "sha512-T98rfsgfdQMS5/mqdsPb6oHSJ+iBYNa+PQDLtXLh6rzTEBsYP9x2uXxIj6VS4qXVDWXVi8rv85NCOG+UBOsHXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.0.8.tgz", + "integrity": "sha512-gVNCi3bYWatdPMeFpFjuZl6bzVL55FkeZU3sPeU+NsMRXC+Zl3qOx3M6cM4OMlJWbhHjYjf2b8q83K0mczaiWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.0.8.tgz", + "integrity": "sha512-uEBGCQIChsixpykL0pjCxfF64btv64vzsb1NoM5u0qvabKvKEvErhXGoqovyldDu9u1T/fswD8Kf6ih0vJEvDQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.0.8.tgz", + "integrity": "sha512-6v0B4sa9ulNezmDZtVpLjNHmA0qZzUl3001YJ2RF0naxsuv/Jq/xEwNYpOzfcdizHfpCE0oBkWzk/r+Slr+0zw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.0.8.tgz", + "integrity": "sha512-lDLGRIMqdwYD39vinwNqqZUxCdL2m2iIdn+0HyQgIHEiT0g5rIAlzaMKzoGWon5NQumfxXFk9y0DarttkR7C1w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@material/animation": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/auto-init": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/banner": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/base": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", + "dependencies": { + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/card": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/checkbox": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/chips": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/data-table": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dom": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/drawer": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/elevation": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/fab": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/feature-targeting": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/floating-label": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/focus-ring": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0" + } + }, + "node_modules/@material/form-field": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/image-list": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/layout-grid": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/linear-progress": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/list": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/notched-outline": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/progress-indicator": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/radio": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/ripple": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/rtl": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", + "dependencies": { + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/segmented-button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/select": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/shape": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/slider": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/snackbar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/switch": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-indicator": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-scroller": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/theme": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tokens": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", + "dependencies": { + "@material/elevation": "15.0.0-canary.7f224ddd4.0" + } + }, + "node_modules/@material/tooltip": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/top-app-bar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/touch-target": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz", + "integrity": "sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz", + "integrity": "sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz", + "integrity": "sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz", + "integrity": "sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz", + "integrity": "sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz", + "integrity": "sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@ngneat/cashew": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@ngneat/cashew/-/cashew-4.1.0.tgz", + "integrity": "sha512-pGWmPIyH4tdmqbgDXqyBkd1ZTE3lsu5xIGqwZTDb66Rr9K/UPzc+ysnNj4RPkSApKC+pfDdO0sVJehNQ60Cjcw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": ">=17.0.0" + } + }, + "node_modules/@ngneat/transloco": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@ngneat/transloco/-/transloco-6.0.4.tgz", + "integrity": "sha512-hQSPdmzuxJIu2SBwvoiwjoUjxSnUGFyCOkJnV8IwzzmBSdgQxqMMci5WXg/bQeCYggA+RyXpUjjTudEvkWy5Rw==", + "dependencies": { + "@ngneat/transloco-utils": "^5.0.0", + "flat": "6.0.1", + "fs-extra": "^11.0.0", + "glob": "^10.0.0", + "lodash.kebabcase": "^4.1.1", + "ora": "^5.4.1", + "replace-in-file": "^7.0.1", + "tslib": "^2.2.0" + }, + "peerDependencies": { + "@angular/core": ">=16.0.0" + } + }, + "node_modules/@ngneat/transloco-utils": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@ngneat/transloco-utils/-/transloco-utils-5.0.0.tgz", + "integrity": "sha512-e0S+GWyBTmLix9KfYWW/rScYdqQz3z3znNSb+foaA5T3jWs4CPLVo+PV0No7kGjqom8Wy8H3lLvztfhHxYSLyA==", + "dependencies": { + "cosmiconfig": "^8.1.3", + "tslib": "^2.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@ngtools/webpack": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-18.0.2.tgz", + "integrity": "sha512-I+ZNFGBnykUWBwGPCXy6m9R2fIX/ovnAUHylvThYd/M+FUfc+Z/3DpKEUBYIOLVCLNZR5nuK0t9QLlazYhWFgg==", + "dev": true, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "@angular/compiler-cli": "^18.0.0", + "typescript": ">=5.4 <5.5", + "webpack": "^5.54.0" + } + }, + "node_modules/@noble/ciphers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz", + "integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/secp256k1": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.1.0.tgz", + "integrity": "sha512-XLEQQNdablO0XZOIniFQimiXsZDNwaYgL96dZwC54Q30imSbAOFf3NKtepc+cXyuZf5Q1HCgbqgZ2UFFuHVcEw==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "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/@npmcli/agent": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", + "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/agent/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", + "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.7.tgz", + "integrity": "sha512-WaOVvto604d5IpdCRV2KjQu8PzkfE96d50CQGKgywXh2GxXmDeUO5EWcBC4V57uFyrNqx83+MewuJh3WTR3xPA==", + "dev": true, + "dependencies": { + "@npmcli/promise-spawn": "^7.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^9.0.0", + "proc-log": "^4.0.0", + "promise-inflight": "^1.0.1", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/git/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/git/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/@npmcli/git/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/installed-package-contents": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", + "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, + "dependencies": { + "npm-bundled": "^3.0.0", + "npm-normalize-package-bin": "^3.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/node-gyp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", + "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.1.0.tgz", + "integrity": "sha512-1aL4TuVrLS9sf8quCLerU3H9J4vtCtgu8VauYozrmEyU57i/EdKleCnsQ7vpnABIH6c9mnTxcH5sFkO3BlV8wQ==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^7.0.0", + "json-parse-even-better-errors": "^3.0.0", + "normalize-package-data": "^6.0.0", + "proc-log": "^4.0.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", + "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, + "dependencies": { + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/promise-spawn/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.0.tgz", + "integrity": "sha512-SEjCPAVHWYUIQR+Yn03kJmrJjZDtJLYpj300m3HV9OTRZNpC5YpbMsM3eTkECyT4aWj8lDr9WeY6TWefpubtYQ==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", + "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, + "dependencies": { + "@npmcli/node-gyp": "^3.0.0", + "@npmcli/package-json": "^5.0.0", + "@npmcli/promise-spawn": "^7.0.0", + "node-gyp": "^10.0.0", + "proc-log": "^4.0.0", + "which": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@schematics/angular": { + "version": "18.0.2", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-18.0.2.tgz", + "integrity": "sha512-qkJs1oxHtneJ6QxDKpxNyneXGDM9SKVj+Bgi8xUAU3FEzpsYmE/aW3MfwYHOZl0pDBO8c2raqLvlyl3dGP6/Gg==", + "dev": true, + "dependencies": { + "@angular-devkit/core": "18.0.2", + "@angular-devkit/schematics": "18.0.2", + "jsonc-parser": "3.2.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT" + }, + "node_modules/@scure/bip32": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.3.1.tgz", + "integrity": "sha512-osvveYtyzdEVbt3OfwwXFr4P2iVBL5u1Q3q4ONBfDY/UpOuXmOlbgwc1xECEboY8wIays8Yt6onaWMUdUbfl0A==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.1.0", + "@noble/hashes": "~1.3.1", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.1.0.tgz", + "integrity": "sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.3.1" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.2.1.tgz", + "integrity": "sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.3.0", + "@scure/base": "~1.1.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sigstore/bundle": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", + "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/core": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", + "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/protobuf-specs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", + "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/sign": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", + "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "make-fetch-happen": "^13.0.1", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/tuf": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", + "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, + "dependencies": { + "@sigstore/protobuf-specs": "^0.3.2", + "tuf-js": "^2.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@sigstore/verify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", + "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.1.0", + "@sigstore/protobuf-specs": "^0.3.2" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", + "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", + "dev": true + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", + "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", + "dev": true, + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", + "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@tufjs/models": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", + "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/bonjour": { + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", + "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chroma-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@types/chroma-js/-/chroma-js-2.4.4.tgz", + "integrity": "sha512-/DTccpHTaKomqussrn+ciEvfW4k6NAHzNzs/sts1TCqg333qNxOhy8TNIoQCmbGG3Tl8KdEhkGAssb1n3mTXiQ==", + "dev": true + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/connect-history-api-fallback": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", + "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", + "dev": true, + "dependencies": { + "@types/express-serve-static-core": "*", + "@types/node": "*" + } + }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", + "dev": true + }, + "node_modules/@types/cors": { + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, + "node_modules/@types/eslint": { + "version": "8.56.5", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.5.tgz", + "integrity": "sha512-u5/YPJHo1tvkSF2CE0USEkxon82Z5DBy2xR+qfyYNszpX9qcs4sT6uq2kBbj4BXY1+DBGDPnrhMZV3pKWGNukw==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.7", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", + "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.1", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz", + "integrity": "sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/highlight.js": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@types/highlight.js/-/highlight.js-10.1.0.tgz", + "integrity": "sha512-77hF2dGBsOgnvZll1vymYiNUtqJ8cJfXPD6GG/2M0aLRc29PkvB7Au6sIDjIEFcSICBhCh2+Pyq6WSRS7LUm6A==", + "deprecated": "This is a stub types definition. highlight.js provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "highlight.js": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, + "node_modules/@types/http-proxy": { + "version": "1.17.14", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.14.tgz", + "integrity": "sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/jasmine": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.4.tgz", + "integrity": "sha512-px7OMFO/ncXxixDe1zR13V1iycqWae0MxTaw62RpFlksUi5QuNWgQJFkTQjIOvrmutJbI7Fp2Y2N1F6D2R4G6w==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ==", + "dev": true + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/luxon": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.4.2.tgz", + "integrity": "sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==", + "dev": true + }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.11.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.25.tgz", + "integrity": "sha512-TBHyJxk2b7HceLVGFcpAUjsa5zIdsPWlR6XHfyGzd0SFu+/NFgQgMAl96MSDZgQDvJAvV6BKsFOrt6zIL09JDw==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/retry": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", + "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-index": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", + "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, + "node_modules/@types/sockjs": { + "version": "0.3.36", + "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", + "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vitejs/plugin-basic-ssl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-1.1.0.tgz", + "integrity": "sha512-wO4Dk/rm8u7RNhOf95ZzcEmC9rYOncYgvq4z3duaJrCgjN8BxAnDVyndanfcJZ0O6XZzHz6Q0hTimxTg8Y9g/A==", + "dev": true, + "engines": { + "node": ">=14.6.0" + }, + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true + }, + "node_modules/@yr/monotone-cubic-spline": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", + "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==" + }, + "node_modules/abbrev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", + "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/adjust-sourcemap-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-4.0.0.tgz", + "integrity": "sha512-OXwN5b9pCUXNQHJpwwD2qP40byEmSgzj8B4ydSN0uMNYWiFmJ6x6KwUllMmfk8Rwu/HJDFR7U8ubsWBoN0Xp0A==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "regex-parser": "^2.2.11" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/adjust-sourcemap-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "8.13.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", + "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.4.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-html-community": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", + "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", + "dev": true, + "engines": [ + "node >= 0.8.0" + ], + "bin": { + "ansi-html": "bin/ansi-html" + } + }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/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/apexcharts": { + "version": "3.49.1", + "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-3.49.1.tgz", + "integrity": "sha512-MqGtlq/KQuO8j0BBsUJYlRG8VBctKwYdwuBtajHgHTmSgUU3Oai+8oYN/rKCXwXzrUlYA+GiMgotAIbXY2BCGw==", + "dependencies": { + "@yr/monotone-cubic-spline": "^1.0.3", + "svg.draggable.js": "^2.2.2", + "svg.easing.js": "^2.0.0", + "svg.filter.js": "^2.0.2", + "svg.pathmorphing.js": "^0.1.3", + "svg.resize.js": "^1.4.3", + "svg.select.js": "^3.0.1" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "dev": true + }, + "node_modules/autoprefixer": { + "version": "10.4.19", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", + "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.23.0", + "caniuse-lite": "^1.0.30001599", + "fraction.js": "^4.3.7", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.3.tgz", + "integrity": "sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==", + "dev": true, + "dependencies": { + "find-cache-dir": "^4.0.0", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "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==" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "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/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "dev": true + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT" + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/bonjour-service": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", + "integrity": "sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3", + "multicast-dns": "^7.2.5" + } + }, + "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": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "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/browserslist": { + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", + "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001587", + "electron-to-chromium": "^1.4.668", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/bundle-name": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", + "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, + "dependencies": { + "run-applescript": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "18.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.3.tgz", + "integrity": "sha512-qXCd4rh6I07cnDqh8V48/94Tc/WSfj+o3Gn6NZ0aZovS255bUx8O13uKxRFd2eWG0xgsco7+YItQNPaa5E85hg==", + "dev": true, + "dependencies": { + "@npmcli/fs": "^3.1.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^4.0.0", + "ssri": "^10.0.0", + "tar": "^6.1.11", + "unique-filename": "^3.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001621", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz", + "integrity": "sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/chroma-js": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chroma-js/-/chroma-js-2.4.2.tgz", + "integrity": "sha512-U9eDw6+wt7V8z5NncY2jJfZa+hUH8XEj8FQHgFJTrUFnJfXYf4Ml4adI2vXZOjqRDpFWtYVWypDfZwnJ+HIR4A==", + "dev": true + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/cliui/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/cliui/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==" + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/common-path-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", + "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "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/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/connect-history-api-fallback": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", + "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true + }, + "node_modules/copy-anything": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz", + "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==", + "dev": true, + "dependencies": { + "is-what": "^3.14.1" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/copy-webpack-plugin": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", + "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", + "dev": true, + "dependencies": { + "fast-glob": "^3.2.11", + "glob-parent": "^6.0.1", + "globby": "^13.1.1", + "normalize-path": "^3.0.0", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/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/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "dev": true, + "dependencies": { + "browserslist": "^4.23.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/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==" + }, + "node_modules/cosmiconfig/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==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/critters": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/critters/-/critters-0.0.22.tgz", + "integrity": "sha512-NU7DEcQZM2Dy8XTKFHxtdnIM/drE312j2T4PCVaSUcS0oBeyT/NImpRw/Ap0zOr/1SE7SgPK9tGPg1WK/sVakw==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "css-select": "^5.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.2", + "htmlparser2": "^8.0.2", + "postcss": "^8.4.23", + "postcss-media-query-parser": "^0.2.3" + } + }, + "node_modules/critters/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/critters/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/critters/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/critters/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/critters/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/critters/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/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/css-loader": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.1.tgz", + "integrity": "sha512-OxIR5P2mjO1PSXk44bWuQ8XtMK4dpEqpIyERCx3ewOo3I8EmbcxMPUc5ScLtQfgXtOojoMv57So4V/C02HQLsw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "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/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, + "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/default-browser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", + "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", + "dev": true, + "dependencies": { + "bundle-name": "^4.1.0", + "default-browser-id": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-browser-id": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", + "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/default-gateway": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-6.0.3.tgz", + "integrity": "sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==", + "dev": true, + "dependencies": { + "execa": "^5.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-lazy-prop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", + "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "dev": true, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "dev": true + }, + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", + "dev": true + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "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/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/dns-packet": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", + "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", + "dev": true, + "dependencies": { + "@leichtgewicht/ip-codec": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "dev": true, + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.699", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.699.tgz", + "integrity": "sha512-I7q3BbQi6e4tJJN5CRcyvxhK0iJb34TV8eJQcgh+fR2fQ8miMgZcEInckCo1U9exDHbfz7DLDnFn8oqH/VcRKw==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "dev": true + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "devOptional": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true + }, + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "optional": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.3.tgz", + "integrity": "sha512-Kgq0/ZsAPzKrbOjCQcjoSmPoWhlcVnGAUo7jvaLHoxW1Drto0KGkR1xBNg2Cp43b9ImvxmPEJZ9xkfcnqPsfBw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.3", + "@esbuild/android-arm": "0.21.3", + "@esbuild/android-arm64": "0.21.3", + "@esbuild/android-x64": "0.21.3", + "@esbuild/darwin-arm64": "0.21.3", + "@esbuild/darwin-x64": "0.21.3", + "@esbuild/freebsd-arm64": "0.21.3", + "@esbuild/freebsd-x64": "0.21.3", + "@esbuild/linux-arm": "0.21.3", + "@esbuild/linux-arm64": "0.21.3", + "@esbuild/linux-ia32": "0.21.3", + "@esbuild/linux-loong64": "0.21.3", + "@esbuild/linux-mips64el": "0.21.3", + "@esbuild/linux-ppc64": "0.21.3", + "@esbuild/linux-riscv64": "0.21.3", + "@esbuild/linux-s390x": "0.21.3", + "@esbuild/linux-x64": "0.21.3", + "@esbuild/netbsd-x64": "0.21.3", + "@esbuild/openbsd-x64": "0.21.3", + "@esbuild/sunos-x64": "0.21.3", + "@esbuild/win32-arm64": "0.21.3", + "@esbuild/win32-ia32": "0.21.3", + "@esbuild/win32-x64": "0.21.3" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.21.3.tgz", + "integrity": "sha512-DMOV+eeVra0yVq3XIojfczdEQsz+RiFnpEj7lqs8Gux9mlTpN7yIbw0a4KzLspn0Uhw6UVEH3nUAidSqc/rcQg==", + "dev": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "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/esrecurse/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/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "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/execa/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/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true + }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dev": true, + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/express/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "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-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==" + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "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-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/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dev": true, + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.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/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/finalhandler/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/find-cache-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", + "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", + "dev": true, + "dependencies": { + "common-path-prefix": "^3.0.0", + "pkg-dir": "^7.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/flat": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-6.0.1.tgz", + "integrity": "sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==", + "bin": { + "flat": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fraction.js": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs-minipass": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", + "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "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==" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "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": "10.3.10", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz", + "integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.5", + "minimatch": "^9.0.1", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0", + "path-scurry": "^1.10.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", + "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", + "dev": true, + "dependencies": { + "dir-glob": "^3.0.1", + "fast-glob": "^3.3.0", + "ignore": "^5.2.4", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/handle-thing": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", + "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", + "dev": true + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/highlight.js": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.9.0.tgz", + "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/hosted-git-info": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", + "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/hosted-git-info/node_modules/lru-cache": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", + "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "dev": true, + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/hpack.js": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", + "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "dev": true, + "dependencies": { + "inherits": "^2.0.1", + "obuf": "^1.0.0", + "readable-stream": "^2.0.1", + "wbuf": "^1.1.0" + } + }, + "node_modules/hpack.js/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hpack.js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "node_modules/hpack.js/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/html-entities": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.5.2.tgz", + "integrity": "sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true + }, + "node_modules/http-deceiver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", + "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "dev": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "dev": true + }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/http-proxy-middleware": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.0.tgz", + "integrity": "sha512-36AV1fIaI2cWRzHo+rbcxhe3M3jUDCNzc4D5zRl57sEWRAxdXYtw7FSQKYY6PDKssiAKjLYypbssHk+xs/kMXw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.10", + "debug": "^4.3.4", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.5" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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/hyperdyperid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", + "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", + "dev": true, + "engines": { + "node": ">=10.18" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "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/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/ignore-walk": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", + "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/image-size": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz", + "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==", + "dev": true, + "optional": true, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immutable": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "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==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/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==", + "engines": { + "node": ">=4" + } + }, + "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/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "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==" + }, + "node_modules/ini": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.2.tgz", + "integrity": "sha512-AMB1mvwR1pyBFY/nSevUX6y8nJWS63/SzUKD3JyQn97s4xgIdgQPT75IRouIiBAN4yLQBUShNYVW0+UG25daCw==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/inquirer": { + "version": "9.2.22", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.22.tgz", + "integrity": "sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.2", + "@ljharb/through": "^2.3.13", + "ansi-escapes": "^4.3.2", + "chalk": "^5.3.0", + "cli-cursor": "^3.1.0", + "cli-width": "^4.1.0", + "external-editor": "^3.1.0", + "lodash": "^4.17.21", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/inquirer/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dev": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", + "dev": true + }, + "node_modules/ipaddr.js": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", + "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "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.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "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-inside-container": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", + "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, + "dependencies": { + "is-docker": "^3.0.0" + }, + "bin": { + "is-inside-container": "cli.js" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", + "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true + }, + "node_modules/is-network-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.1.0.tgz", + "integrity": "sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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-plain-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.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/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-what": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz", + "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==", + "dev": true + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", + "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", + "dev": true, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report/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/istanbul-lib-report/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/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps/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/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jasmine-core": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.1.2.tgz", + "integrity": "sha512-2oIUMGn00FdUiqz6epiiJr7xcFyNYj3rDcfmnzfkBnHyBQ3cBQUs4mmyGsOb7TTLb9kxk7dBcmEmqhDKkBoDyA==", + "dev": true + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/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/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", + "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-parser": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", + "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "dev": true + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, + "engines": [ + "node >= 0.2.0" + ] + }, + "node_modules/karma": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.3.tgz", + "integrity": "sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q==", + "dev": true, + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", + "rimraf": "^3.0.2", + "socket.io": "^4.7.2", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" + }, + "bin": { + "karma": "bin/karma" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-3.2.0.tgz", + "integrity": "sha512-rE9RkUPI7I9mAxByQWkGJFXfFD6lE4gC5nPuZdobf/QdTEJI6EU4yIay/cfU/xV4ZxlM5JiTv7zWYgA64NpS5Q==", + "dev": true, + "dependencies": { + "which": "^1.2.1" + } + }, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/karma-coverage": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/karma-coverage/-/karma-coverage-2.2.1.tgz", + "integrity": "sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.1", + "istanbul-reports": "^3.0.5", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/karma-coverage/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/karma-coverage/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/karma-jasmine": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine/-/karma-jasmine-5.1.0.tgz", + "integrity": "sha512-i/zQLFrfEpRyQoJF9fsCdTMOF5c2dK7C7OmsuKg2D0YSsuZSfQDiLuaiktbuio6F2wiCsZSnSnieIQ0ant/uzQ==", + "dev": true, + "dependencies": { + "jasmine-core": "^4.1.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "karma": "^6.0.0" + } + }, + "node_modules/karma-jasmine-html-reporter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz", + "integrity": "sha512-sPQE1+nlsn6Hwb5t+HHwyy0A1FNCVKuL1192b+XNauMYWThz2kweiBVW1DqloRpVvZIJkIoHVB7XRpK78n1xbQ==", + "dev": true, + "peerDependencies": { + "jasmine-core": "^4.0.0 || ^5.0.0", + "karma": "^6.0.0", + "karma-jasmine": "^5.0.0" + } + }, + "node_modules/karma-jasmine/node_modules/jasmine-core": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.6.0.tgz", + "integrity": "sha512-O236+gd0ZXS8YAjFx8xKaJ94/erqUliEkJTDedyE7iHvv4ZVqi+q+8acJxu05/WJDKm512EUNn809In37nWlAQ==", + "dev": true + }, + "node_modules/karma-source-map-support": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/karma-source-map-support/-/karma-source-map-support-1.4.0.tgz", + "integrity": "sha512-RsBECncGO17KAoJCYXjv+ckIz+Ii9NCi+9enk+rq6XC81ezYkb4/RHE6CTXdA7IOJqoF3wcaLfVG0CPmE5ca6A==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.5" + } + }, + "node_modules/karma/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/karma/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/karma/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/karma/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/karma/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/karma/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/karma/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/karma/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/karma/node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/karma/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/karma/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/karma/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/launch-editor": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.1.tgz", + "integrity": "sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "shell-quote": "^1.8.1" + } + }, + "node_modules/less": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", + "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", + "dev": true, + "dependencies": { + "copy-anything": "^2.0.1", + "parse-node-version": "^1.0.1", + "tslib": "^2.3.0" + }, + "bin": { + "lessc": "bin/lessc" + }, + "engines": { + "node": ">=6" + }, + "optionalDependencies": { + "errno": "^0.1.1", + "graceful-fs": "^4.1.2", + "image-size": "~0.5.0", + "make-dir": "^2.1.0", + "mime": "^1.4.1", + "needle": "^3.1.0", + "source-map": "~0.6.0" + } + }, + "node_modules/less-loader": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/less-loader/-/less-loader-12.2.0.tgz", + "integrity": "sha512-MYUxjSQSBUQmowc0l5nPieOYwMzGPUaTzB6inNW/bdPEG9zOL3eAAD1Qw5ZxSPk7we5dMojHwNODYMV1hq4EVg==", + "dev": true, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "less": "^3.5.0 || ^4.0.0", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/less/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "optional": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/less/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/less/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "optional": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/less/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, + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/license-webpack-plugin": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", + "integrity": "sha512-771TFWFD70G1wLTC4oU2Cw4qvtmNrIw+wRvBtn+okgHl7slJVi7zfNcdmqDL72BojM30VNJ2UHylr1o77U37Jw==", + "dev": true, + "dependencies": { + "webpack-sources": "^3.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-sources": { + "optional": true + } + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lmdb": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/lmdb/-/lmdb-3.0.8.tgz", + "integrity": "sha512-9rp8JT4jPhCRJUL7vRARa2N06OLSYzLwQsEkhC6Qu5XbcLyM/XBLMzDlgS/K7l7c5CdURLdDk9uE+hPFIogHTQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "msgpackr": "^1.9.9", + "node-addon-api": "^6.1.0", + "node-gyp-build-optional-packages": "5.1.1", + "ordered-binary": "^1.4.1", + "weak-lru-cache": "^1.2.2" + }, + "bin": { + "download-lmdb-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@lmdb/lmdb-darwin-arm64": "3.0.8", + "@lmdb/lmdb-darwin-x64": "3.0.8", + "@lmdb/lmdb-linux-arm": "3.0.8", + "@lmdb/lmdb-linux-arm64": "3.0.8", + "@lmdb/lmdb-linux-x64": "3.0.8", + "@lmdb/lmdb-win32-x64": "3.0.8" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-3.2.1.tgz", + "integrity": "sha512-ZvFw1KWS3GVyYBYb7qkmRM/WwL2TQQBxgCK62rlvm4WpVQ23Nb4tYjApUlfjrEGvOs7KHEsmyUn75OHZrJMWPw==", + "dev": true, + "engines": { + "node": ">= 12.13.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "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-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, + "node_modules/lodash.kebabcase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", + "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==" + }, + "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/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/log-symbols/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/log-symbols/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==" + }, + "node_modules/log-symbols/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/log4js": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.9.1.tgz", + "integrity": "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/luxon": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", + "integrity": "sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-fetch-happen": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", + "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, + "dependencies": { + "@npmcli/agent": "^2.0.0", + "cacache": "^18.0.0", + "http-cache-semantics": "^4.1.1", + "is-lambda": "^1.0.1", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "proc-log": "^4.2.0", + "promise-retry": "^2.0.1", + "ssri": "^10.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memfs": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.9.2.tgz", + "integrity": "sha512-f16coDZlTG1jskq3mxarwB+fGRrd0uXWt+o1WIhRfOwbXQZqUDsTVxQBFK9JjRQHblg8eAG2JSbprDXKjc7ijQ==", + "dev": true, + "dependencies": { + "@jsonjoy.com/json-pack": "^1.0.3", + "@jsonjoy.com/util": "^1.1.2", + "sonic-forest": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">= 4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==", + "dev": true + }, + "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/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "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/micromatch/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/mime": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mini-css-extract-plugin": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.0.tgz", + "integrity": "sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==", + "dev": true, + "dependencies": { + "schema-utils": "^4.0.0", + "tapable": "^2.2.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-collect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", + "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/minipass-fetch": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", + "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush/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/minipass-json-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", + "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", + "dev": true, + "dependencies": { + "jsonparse": "^1.3.1", + "minipass": "^3.0.0" + } + }, + "node_modules/minipass-json-stream/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-json-stream/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/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/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/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/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/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib/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/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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/msgpackr": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.10.2.tgz", + "integrity": "sha512-L60rsPynBvNE+8BWipKKZ9jHcSGbtyJYIwjRq0VrIvQ08cRjntGXJYW/tmciZ2IHWIY8WEW32Qa2xbh5+SKBZA==", + "dev": true, + "optionalDependencies": { + "msgpackr-extract": "^3.0.2" + } + }, + "node_modules/msgpackr-extract": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz", + "integrity": "sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "dependencies": { + "node-gyp-build-optional-packages": "5.0.7" + }, + "bin": { + "download-msgpackr-prebuilds": "bin/download-prebuilds.js" + }, + "optionalDependencies": { + "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.2", + "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.2" + } + }, + "node_modules/msgpackr-extract/node_modules/node-gyp-build-optional-packages": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz", + "integrity": "sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/multicast-dns": { + "version": "7.2.5", + "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", + "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", + "dev": true, + "dependencies": { + "dns-packet": "^5.2.2", + "thunky": "^1.0.2" + }, + "bin": { + "multicast-dns": "cli.js" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/needle": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz", + "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==", + "dev": true, + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/ng-apexcharts": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/ng-apexcharts/-/ng-apexcharts-1.10.0.tgz", + "integrity": "sha512-Bg85iL3uAuALaj7yQ29XsPYGx8cr3gDrlBPYQtWzEbHcIHP1PBl9IEH9CLF7ESxypW/XPbliH3pc95o5+9gIag==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": "^17.0.2", + "@angular/core": "^17.0.2", + "apexcharts": "^3.45.2", + "rxjs": "^6.5.5 || ^7.4.0" + } + }, + "node_modules/ngx-indexed-db": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/ngx-indexed-db/-/ngx-indexed-db-19.0.0.tgz", + "integrity": "sha512-6GLb1ZJdwD/8o3GWpW0J/Xyvd95xP/pRtYu6JE5NX4Har0JHqDL+Y98r8eD11g3YNnSmzHW2V93Ef6ncbx3IzQ==", + "license": "ISC", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@angular/common": ">=10.0.6", + "@angular/core": ">=10.0.6" + } + }, + "node_modules/ngx-quill": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/ngx-quill/-/ngx-quill-26.0.1.tgz", + "integrity": "sha512-jiG4YKrGRdtv3g3jno36N5nHNuyCnOmGcHWnvadNy/w/T+MTFEkjyIN4if9KVPWqXmaXkC3/07njNuWpCJRtSQ==", + "dependencies": { + "tslib": "^2.3.0" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0" + }, + "peerDependencies": { + "@angular/core": "^18.0.0", + "quill": "^2.0.0", + "rxjs": "^7.0.0" + } + }, + "node_modules/nice-napi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nice-napi/-/nice-napi-1.0.2.tgz", + "integrity": "sha512-px/KnJAJZf5RuBGcfD+Sp2pAKq0ytz8j+1NehvgIGFkvtvFrDM3T8E4x/JJODXK9WZow8RRGrbA9QQ3hs+pDhA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "!win32" + ], + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2" + } + }, + "node_modules/nice-napi/node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true, + "optional": true + }, + "node_modules/node-addon-api": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz", + "integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==", + "dev": true + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-gyp": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.1.0.tgz", + "integrity": "sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "glob": "^10.3.10", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^13.0.0", + "nopt": "^7.0.0", + "proc-log": "^3.0.0", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^4.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz", + "integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==", + "dev": true, + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-gyp-build-optional-packages": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz", + "integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.1" + }, + "bin": { + "node-gyp-build-optional-packages": "bin.js", + "node-gyp-build-optional-packages-optional": "optional.js", + "node-gyp-build-optional-packages-test": "build-test.js" + } + }, + "node_modules/node-gyp/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-3.0.0.tgz", + "integrity": "sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-gyp/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/nopt": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", + "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, + "dependencies": { + "abbrev": "^2.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.1.tgz", + "integrity": "sha512-6rvCfeRW+OEZagAB4lMLSNuTNYZWLVtKccK79VSTf//yTY5VOCgcpH80O+bZK8Neps7pUnd5G+QlMg1yV/2iZQ==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "is-core-module": "^2.8.1", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "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/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nostr-tools": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.2.tgz", + "integrity": "sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==", + "license": "Unlicense", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/nostr-tools/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/nostr-wasm": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/nostr-wasm/-/nostr-wasm-0.1.0.tgz", + "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", + "license": "MIT", + "optional": true + }, + "node_modules/npm-bundled": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", + "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^3.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-install-checks": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", + "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-package-arg": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.2.tgz", + "integrity": "sha512-IGN0IAwmhDJwy13Wc8k+4PEbTPhpJnMtfR53ZbOyjkvmEcLS4nCwp6mvMWjS5sUjeiW3mpx6cHmuhKEu9XmcQw==", + "dev": true, + "dependencies": { + "hosted-git-info": "^7.0.0", + "proc-log": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^5.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-packlist": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", + "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, + "dependencies": { + "ignore-walk": "^6.0.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm-pick-manifest": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.0.1.tgz", + "integrity": "sha512-Udm1f0l2nXb3wxDpKjfohwgdFUSV50UVwzEIpDXVsbDMXVIEF81a/i0UhuQbhrPMMmdiq3+YMFLFIRVLs3hxQw==", + "dev": true, + "dependencies": { + "npm-install-checks": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "npm-package-arg": "^11.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm-registry-fetch": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.0.1.tgz", + "integrity": "sha512-fLu9MTdZTlJAHUek/VLklE6EpIiP3VZpTiuN7OOMCt2Sd67NCpSEetMaxHHEZiZxllp8ZLsUpvbEszqTFEc+wA==", + "dev": true, + "dependencies": { + "@npmcli/redact": "^2.0.0", + "make-fetch-happen": "^13.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^3.0.0", + "minipass-json-stream": "^1.0.1", + "minizlib": "^2.1.2", + "npm-package-arg": "^11.0.0", + "proc-log": "^4.0.0" + }, + "engines": { + "node": "^16.14.0 || >=18.0.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/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obuf": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", + "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", + "dev": true + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", + "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", + "dev": true, + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/ora/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ora/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==" + }, + "node_modules/ora/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ora/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ordered-binary": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.5.1.tgz", + "integrity": "sha512-5VyHfHY3cd0iza71JepYG50My+YUbrFtGoUz2ooEydPyPM7Aai/JW098juLr+RG6+rDJuzNNTsEQu2DZa1A41A==", + "dev": true + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.0.tgz", + "integrity": "sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA==", + "dev": true, + "dependencies": { + "@types/retry": "0.12.2", + "is-network-error": "^1.0.0", + "retry": "^0.13.1" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-retry/node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pacote": { + "version": "18.0.6", + "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", + "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, + "dependencies": { + "@npmcli/git": "^5.0.0", + "@npmcli/installed-package-contents": "^2.0.1", + "@npmcli/package-json": "^5.1.0", + "@npmcli/promise-spawn": "^7.0.0", + "@npmcli/run-script": "^8.0.0", + "cacache": "^18.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^11.0.0", + "npm-packlist": "^8.0.0", + "npm-pick-manifest": "^9.0.0", + "npm-registry-fetch": "^17.0.0", + "proc-log": "^4.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^2.2.0", + "ssri": "^10.0.0", + "tar": "^6.1.11" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/parchment": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz", + "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==" + }, + "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==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-json/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/parse-node-version": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz", + "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "devOptional": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-html-rewriting-stream": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-html-rewriting-stream/-/parse5-html-rewriting-stream-7.0.0.tgz", + "integrity": "sha512-mazCyGWkmCRWDI15Zp+UiCqMp/0dgEmkZRvhlsqqKYr4SsVm/TvnSpD9fCvqCA2zoWJcfRym846ejWBBHRiYEg==", + "dev": true, + "dependencies": { + "entities": "^4.3.0", + "parse5": "^7.0.0", + "parse5-sax-parser": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-sax-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-sax-parser/-/parse5-sax-parser-7.0.0.tgz", + "integrity": "sha512-5A+v2SNsq8T6/mG3ahcz8ZtQ0OUFTatxPbeidoMB7tkJSGDY3tdfl4MHovtLQHkEn5CGxijNWRQHhRQ6IRpXKg==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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==", + "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-scurry": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz", + "integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==", + "dependencies": { + "lru-cache": "^9.1.1 || ^10.0.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/piscina": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/piscina/-/piscina-4.5.0.tgz", + "integrity": "sha512-iBaLWI56PFP81cfBSomWTmhOo9W2/yhIOL+Tk8O1vBCpK39cM0tGxB+wgYjG31qq4ohGvysfXSdnj8h7g4rZxA==", + "dev": true, + "optionalDependencies": { + "nice-napi": "^1.0.2" + } + }, + "node_modules/pkg-dir": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", + "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", + "dev": true, + "dependencies": { + "find-up": "^6.3.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "dev": true, + "dependencies": { + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "dev": true, + "dependencies": { + "p-locate": "^6.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "dev": true, + "dependencies": { + "p-limit": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-loader": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.1.1.tgz", + "integrity": "sha512-0IeqyAsG6tYiDRCYKQJLAmgQr47DX6N7sFSWvQxt6AcupX8DIdmykuk/o/tx0Lze3ErGHJEp5OSRxrelC6+NdQ==", + "dev": true, + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^1.20.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/postcss-loader/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/postcss-loader/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/postcss-loader/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/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", + "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.5.tgz", + "integrity": "sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.0.tgz", + "integrity": "sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=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/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/prettier": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz", + "integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", + "dev": true, + "peerDependencies": { + "@volar/vue-language-plugin-pug": "^1.0.4", + "@volar/vue-typescript": "^1.0.4", + "prettier": ">=2.0", + "typescript": ">=2.9" + }, + "peerDependenciesMeta": { + "@volar/vue-language-plugin-pug": { + "optional": true + }, + "@volar/vue-typescript": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.1.tgz", + "integrity": "sha512-AnbeYZu0WGj+QgKciUgdMnRxrqcxltleZPgdwfA5104BHM3siBLONN/HLW1yS2HvzSNkzpQ/JAj+LN0jcJO+0w==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig-melody": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig-melody": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/proc-log": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", + "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true, + "optional": true + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/qjobs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", + "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "dev": true, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/quill": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.2.tgz", + "integrity": "sha512-QfazNrhMakEdRG57IoYFwffUIr04LWJxbS/ZkidRFXYCQt63c1gK6Z7IHUXMx/Vh25WgPBU42oBaNzQ0K1R/xw==", + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash-es": "^4.17.21", + "parchment": "^3.0.0", + "quill-delta": "^5.1.0" + }, + "engines": { + "npm": ">=8.2.3" + } + }, + "node_modules/quill-delta": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz", + "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==", + "dependencies": { + "fast-diff": "^1.3.0", + "lodash.clonedeep": "^4.5.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/quill/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dev": true, + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/readdirp/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/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "dev": true + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.8.4" + } + }, + "node_modules/regex-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-parser/-/regex-parser-2.3.0.tgz", + "integrity": "sha512-TVILVSz2jY5D47F4mA4MppkBrafEaiUWJO/TcZHEIuI13AqoZMkK1WMA4Om1YkYbTx+9Ki1/tSUXbceyr9saRg==", + "dev": true + }, + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "dependencies": { + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" + } + }, + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + } + }, + "node_modules/replace-in-file": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/replace-in-file/-/replace-in-file-7.1.0.tgz", + "integrity": "sha512-1uZmJ78WtqNYCSuPC9IWbweXkGxPOtk2rKuar8diTw7naVIQZiE3Tm8ACx2PCMXDtVH6N+XxwaRY2qZ2xHPqXw==", + "dependencies": { + "chalk": "^4.1.2", + "glob": "^8.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "replace-in-file": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/replace-in-file/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/replace-in-file/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/replace-in-file/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==" + }, + "node_modules/replace-in-file/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/replace-in-file/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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/replace-in-file/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/replace-in-file/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==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.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": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-url-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-url-loader/-/resolve-url-loader-5.0.0.tgz", + "integrity": "sha512-uZtduh8/8srhBoMx//5bwqjQ+rfYOUq8zC9NrMUGtjBiGTtFJM42s58/36+hTqeqINcnYe08Nj3LkK9lW4N8Xg==", + "dev": true, + "dependencies": { + "adjust-sourcemap-loader": "^4.0.0", + "convert-source-map": "^1.7.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.14", + "source-map": "0.6.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/resolve-url-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/resolve-url-loader/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/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/restore-cursor/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==" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "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/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "dev": true + }, + "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/rimraf/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/rimraf/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/rimraf/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/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-applescript": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", + "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "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/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==" + }, + "node_modules/sass": { + "version": "1.77.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz", + "integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==", + "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": ">=14.0.0" + } + }, + "node_modules/sass-loader": { + "version": "14.2.1", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-14.2.1.tgz", + "integrity": "sha512-G0VcnMYU18a4N7VoNDegg2OuMjYtxnqzQWARVWCIVSZwJeiL9kg8QMsuIZOplsJgTzZLF6jGxI3AClj8I9nRdQ==", + "dev": true, + "dependencies": { + "neo-async": "^2.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "node-sass": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", + "sass": "^1.3.0", + "sass-embedded": "*", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "node-sass": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/sax": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", + "dev": true, + "optional": true + }, + "node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/select-hose": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", + "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "dev": true + }, + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", + "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver/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/semver/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/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/serve-index": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-index/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-index/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-index/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, + "node_modules/serve-index/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/serve-index/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sigstore": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", + "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, + "dependencies": { + "@sigstore/bundle": "^2.3.2", + "@sigstore/core": "^1.0.0", + "@sigstore/protobuf-specs": "^0.3.2", + "@sigstore/sign": "^2.3.2", + "@sigstore/tuf": "^2.3.4", + "@sigstore/verify": "^1.2.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "dev": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socket.io": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.4.tgz", + "integrity": "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", + "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.11.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dev": true, + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/sockjs": { + "version": "0.3.24", + "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", + "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", + "dev": true, + "dependencies": { + "faye-websocket": "^0.11.3", + "uuid": "^8.3.2", + "websocket-driver": "^0.7.4" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dev": true, + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.3.tgz", + "integrity": "sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.7.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/sonic-forest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sonic-forest/-/sonic-forest-1.0.3.tgz", + "integrity": "sha512-dtwajos6IWMEWXdEbW1IkEkyL2gztCAgDplRIX+OT5aRKnEd5e7r7YCxRgXZdhRP1FBdOBf8axeTPhzDv8T4wQ==", + "dev": true, + "dependencies": { + "tree-dump": "^1.0.0" + }, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-loader": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/source-map-loader/-/source-map-loader-5.0.0.tgz", + "integrity": "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA==", + "dev": true, + "dependencies": { + "iconv-lite": "^0.6.3", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.72.1" + } + }, + "node_modules/source-map-loader/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/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/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", + "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "dev": true + }, + "node_modules/spdy": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", + "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "handle-thing": "^2.0.0", + "http-deceiver": "^1.2.7", + "select-hose": "^2.0.0", + "spdy-transport": "^3.0.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/spdy-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", + "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "detect-node": "^2.0.4", + "hpack.js": "^2.1.6", + "obuf": "^1.1.2", + "readable-stream": "^3.0.6", + "wbuf": "^1.7.3" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", + "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/streamroller": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.5.tgz", + "integrity": "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==", + "dev": true, + "dependencies": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/streamroller/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/streamroller/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "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/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/svg.draggable.js": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz", + "integrity": "sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==", + "dependencies": { + "svg.js": "^2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.easing.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svg.easing.js/-/svg.easing.js-2.0.0.tgz", + "integrity": "sha512-//ctPdJMGy22YoYGV+3HEfHbm6/69LJUTAqI2/5qBvaNHZ9uUFVC82B0Pl299HzgH13rKrBgi4+XyXXyVWWthA==", + "dependencies": { + "svg.js": ">=2.3.x" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.filter.js": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/svg.filter.js/-/svg.filter.js-2.0.2.tgz", + "integrity": "sha512-xkGBwU+dKBzqg5PtilaTb0EYPqPfJ9Q6saVldX+5vCRy31P6TlRCP3U9NxH3HEufkKkpNgdTLBJnmhDHeTqAkw==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/svg.js/-/svg.js-2.7.1.tgz", + "integrity": "sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==" + }, + "node_modules/svg.pathmorphing.js": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz", + "integrity": "sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==", + "dependencies": { + "svg.js": "^2.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/svg.resize.js/-/svg.resize.js-1.4.3.tgz", + "integrity": "sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==", + "dependencies": { + "svg.js": "^2.6.5", + "svg.select.js": "^2.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.resize.js/node_modules/svg.select.js": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-2.1.2.tgz", + "integrity": "sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==", + "dependencies": { + "svg.js": "^2.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/svg.select.js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/svg.select.js/-/svg.select.js-3.0.1.tgz", + "integrity": "sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==", + "dependencies": { + "svg.js": "^2.6.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/symbol-observable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", + "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", + "dev": true, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/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/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.0.15", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", + "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar/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/terser": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", + "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser-webpack-plugin": { + "version": "5.3.10", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz", + "integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.20", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.26.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/test-exclude/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/test-exclude/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/test-exclude/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/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thingies": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/thingies/-/thingies-1.21.0.tgz", + "integrity": "sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g==", + "dev": true, + "engines": { + "node": ">=10.18" + }, + "peerDependencies": { + "tslib": "^2" + } + }, + "node_modules/thunky": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", + "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tree-dump": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.1.tgz", + "integrity": "sha512-WCkcRBVPSlHHq1dc/px9iOfqklvzCbdRwvlNfxGZsrHqf6aZttfPrd7DJTt6oR10dwUfpFFQeVTkPbBIZxX/YA==", + "dev": true, + "engines": { + "node": ">=10.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/streamich" + }, + "peerDependencies": { + "tslib": "2" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/tuf-js": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", + "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, + "dependencies": { + "@tufjs/models": "2.0.1", + "debug": "^4.3.4", + "make-fetch-happen": "^13.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dev": true, + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-assert": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/typed-assert/-/typed-assert-1.0.9.tgz", + "integrity": "sha512-KNNZtayBCtmnNmbo5mG47p1XsCyrx6iVqomjcZnec/1Y5GGARaxPs6r49RnSPeUP3YjNYiU9sQHAtY4BBvnZwg==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "node_modules/undici": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.18.0.tgz", + "integrity": "sha512-nT8jjv/fE9Et1ilR6QoW8ingRTY2Pp4l2RUrdzV5Yz35RJDrtPc1DXvuNqcpsJSGIRHFdt3YKKktTzJA6r0fTA==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/unique-filename": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", + "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, + "dependencies": { + "unique-slug": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/unique-slug": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", + "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "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==" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/validate-npm-package-name": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", + "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha512-qZKX4RnBzH2ugr8Lxa7x+0V6XD9Sb/ouARtiasEQCHB1EVU4NXtmHsDDrx1dO4ne5fc3J6EW05BP1Dl0z0iung==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/watchpack": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.1.tgz", + "integrity": "sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/wbuf": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", + "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", + "dev": true, + "dependencies": { + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/weak-lru-cache": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz", + "integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==", + "dev": true + }, + "node_modules/webpack": { + "version": "5.91.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", + "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^1.0.5", + "@webassemblyjs/ast": "^1.12.1", + "@webassemblyjs/wasm-edit": "^1.12.1", + "@webassemblyjs/wasm-parser": "^1.12.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.21.10", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.16.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.11", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.10", + "watchpack": "^2.4.1", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-middleware": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz", + "integrity": "sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA==", + "dev": true, + "dependencies": { + "colorette": "^2.0.10", + "memfs": "^4.6.0", + "mime-types": "^2.1.31", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz", + "integrity": "sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA==", + "dev": true, + "dependencies": { + "@types/bonjour": "^3.5.13", + "@types/connect-history-api-fallback": "^1.5.4", + "@types/express": "^4.17.21", + "@types/serve-index": "^1.9.4", + "@types/serve-static": "^1.15.5", + "@types/sockjs": "^0.3.36", + "@types/ws": "^8.5.10", + "ansi-html-community": "^0.0.8", + "bonjour-service": "^1.2.1", + "chokidar": "^3.6.0", + "colorette": "^2.0.10", + "compression": "^1.7.4", + "connect-history-api-fallback": "^2.0.0", + "default-gateway": "^6.0.3", + "express": "^4.17.3", + "graceful-fs": "^4.2.6", + "html-entities": "^2.4.0", + "http-proxy-middleware": "^2.0.3", + "ipaddr.js": "^2.1.0", + "launch-editor": "^2.6.1", + "open": "^10.0.3", + "p-retry": "^6.2.0", + "rimraf": "^5.0.5", + "schema-utils": "^4.2.0", + "selfsigned": "^2.4.1", + "serve-index": "^1.9.1", + "sockjs": "^0.3.24", + "spdy": "^4.0.2", + "webpack-dev-middleware": "^7.1.0", + "ws": "^8.16.0" + }, + "bin": { + "webpack-dev-server": "bin/webpack-dev-server.js" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "webpack": { + "optional": true + }, + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", + "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/http-proxy-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", + "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", + "dev": true, + "dependencies": { + "@types/http-proxy": "^1.17.8", + "http-proxy": "^1.18.1", + "is-glob": "^4.0.1", + "is-plain-obj": "^3.0.0", + "micromatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "@types/express": "^4.17.13" + }, + "peerDependenciesMeta": { + "@types/express": { + "optional": true + } + } + }, + "node_modules/webpack-dev-server/node_modules/is-wsl": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", + "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/open": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", + "integrity": "sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==", + "dev": true, + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "is-wsl": "^3.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/webpack-dev-server/node_modules/rimraf": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz", + "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==", + "dev": true, + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/webpack-dev-server/node_modules/ws": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", + "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/webpack-merge": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", + "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "flat": "^5.0.2", + "wildcard": "^2.0.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/webpack-merge/node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack-subresource-integrity": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/webpack-subresource-integrity/-/webpack-subresource-integrity-5.1.0.tgz", + "integrity": "sha512-sacXoX+xd8r4WKsy9MvH/q/vBtEHr86cpImXwyg74pFIpERKt6FmB8cXpeuh0ZLgclOlHI4Wcll7+R5L02xk9Q==", + "dev": true, + "dependencies": { + "typed-assert": "^1.0.8" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "html-webpack-plugin": ">= 5.0.0-beta.1 < 6", + "webpack": "^5.12.0" + }, + "peerDependenciesMeta": { + "html-webpack-plugin": { + "optional": true + } + } + }, + "node_modules/webpack/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/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/webpack/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/webpack/node_modules/schema-utils": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", + "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dev": true, + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wildcard": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", + "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/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==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/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==" + }, + "node_modules/wrap-ansi/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/wrap-ansi/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/wrap-ansi/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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", + "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "dev": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", + "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", + "dev": true, + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zone.js": { + "version": "0.14.6", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.6.tgz", + "integrity": "sha512-vyRNFqofdaHVdWAy7v3Bzmn84a1JHWSjpuTZROT/uYn8I3p2cmo7Ro9twFmYRQDPhiYOV7QLk0hhY4JJQVqS6Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c58213b --- /dev/null +++ b/package.json @@ -0,0 +1,77 @@ +{ + "name": "angor-browse", + "version": "0.0.1", + "description": "Angor Hub- Decentralized Crowdfunding<", + "author": "https://themeforest.net/user/srcn", + "license": "https://themeforest.net/licenses/standard", + "private": true, + "scripts": { + "ng": "ng", + "start": "ng serve", + "build": "ng build", + "watch": "ng build --watch --configuration development", + "test": "ng test" + }, + "dependencies": { + "@angular/animations": "18.0.1", + "@angular/cdk": "18.0.1", + "@angular/common": "18.0.1", + "@angular/compiler": "18.0.1", + "@angular/core": "18.0.1", + "@angular/forms": "18.0.1", + "@angular/material": "18.0.1", + "@angular/material-luxon-adapter": "18.0.1", + "@angular/platform-browser": "18.0.1", + "@angular/platform-browser-dynamic": "18.0.1", + "@angular/router": "18.0.1", + "@gandlaf21/bolt11-decode": "^3.1.1", + "@ngneat/cashew": "^4.1.0", + "@ngneat/transloco": "6.0.4", + "@noble/hashes": "^1.5.0", + "@noble/secp256k1": "^2.1.0", + "apexcharts": "3.49.1", + "bech32": "^2.0.0", + "crypto-js": "4.2.0", + "dayjs": "^1.11.13", + "highlight.js": "11.9.0", + "lodash-es": "4.17.21", + "luxon": "3.4.4", + "ng-apexcharts": "1.10.0", + "ngx-indexed-db": "^19.0.0", + "ngx-quill": "26.0.1", + "nostr-tools": "^2.7.2", + "perfect-scrollbar": "1.5.5", + "quill": "2.0.2", + "rxjs": "7.8.1", + "tslib": "2.6.2", + "zone.js": "0.14.6" + }, + "devDependencies": { + "@angular-devkit/build-angular": "18.0.2", + "@angular/cli": "18.0.2", + "@angular/compiler-cli": "18.0.1", + "@tailwindcss/typography": "0.5.13", + "@types/chroma-js": "2.4.4", + "@types/crypto-js": "4.2.2", + "@types/highlight.js": "10.1.0", + "@types/jasmine": "5.1.4", + "@types/lodash": "4.17.4", + "@types/lodash-es": "4.17.12", + "@types/luxon": "3.4.2", + "autoprefixer": "10.4.19", + "chroma-js": "2.4.2", + "jasmine-core": "5.1.2", + "karma": "6.4.3", + "karma-chrome-launcher": "3.2.0", + "karma-coverage": "2.2.1", + "karma-jasmine": "5.1.0", + "karma-jasmine-html-reporter": "2.1.0", + "lodash": "4.17.21", + "postcss": "8.4.38", + "prettier": "3.3.0", + "prettier-plugin-organize-imports": "3.2.4", + "prettier-plugin-tailwindcss": "0.6.1", + "tailwindcss": "3.4.3", + "typescript": "5.4.5" + } +} diff --git a/public/.gitkeep b/public/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/data/emoji.json b/public/data/emoji.json new file mode 100644 index 0000000..6aa7136 --- /dev/null +++ b/public/data/emoji.json @@ -0,0 +1,7612 @@ +{ + "Smileys & Emotion": [ + { + "emoji": "😀", + "name": "grinning face" + }, + { + "emoji": "😃", + "name": "grinning face with big eyes" + }, + { + "emoji": "😄", + "name": "grinning face with smiling eyes" + }, + { + "emoji": "😁", + "name": "beaming face with smiling eyes" + }, + { + "emoji": "😆", + "name": "grinning squinting face" + }, + { + "emoji": "😅", + "name": "grinning face with sweat" + }, + { + "emoji": "🤣", + "name": "rolling on the floor laughing" + }, + { + "emoji": "😂", + "name": "face with tears of joy" + }, + { + "emoji": "🙂", + "name": "slightly smiling face" + }, + { + "emoji": "🙃", + "name": "upside-down face" + }, + { + "emoji": "🫠", + "name": "melting face" + }, + { + "emoji": "😉", + "name": "winking face" + }, + { + "emoji": "😊", + "name": "smiling face with smiling eyes" + }, + { + "emoji": "😇", + "name": "smiling face with halo" + }, + { + "emoji": "🥰", + "name": "smiling face with hearts" + }, + { + "emoji": "😍", + "name": "smiling face with heart-eyes" + }, + { + "emoji": "🤩", + "name": "star-struck" + }, + { + "emoji": "😘", + "name": "face blowing a kiss" + }, + { + "emoji": "😗", + "name": "kissing face" + }, + { + "emoji": "☺️", + "name": "smiling face" + }, + { + "emoji": "😚", + "name": "kissing face with closed eyes" + }, + { + "emoji": "😙", + "name": "kissing face with smiling eyes" + }, + { + "emoji": "🥲", + "name": "smiling face with tear" + }, + { + "emoji": "😋", + "name": "face savoring food" + }, + { + "emoji": "😛", + "name": "face with tongue" + }, + { + "emoji": "😜", + "name": "winking face with tongue" + }, + { + "emoji": "🤪", + "name": "zany face" + }, + { + "emoji": "😝", + "name": "squinting face with tongue" + }, + { + "emoji": "🤑", + "name": "money-mouth face" + }, + { + "emoji": "🤗", + "name": "smiling face with open hands" + }, + { + "emoji": "🤭", + "name": "face with hand over mouth" + }, + { + "emoji": "🫢", + "name": "face with open eyes and hand over mouth" + }, + { + "emoji": "🫣", + "name": "face with peeking eye" + }, + { + "emoji": "🤫", + "name": "shushing face" + }, + { + "emoji": "🤔", + "name": "thinking face" + }, + { + "emoji": "🫡", + "name": "saluting face" + }, + { + "emoji": "🤐", + "name": "zipper-mouth face" + }, + { + "emoji": "🤨", + "name": "face with raised eyebrow" + }, + { + "emoji": "😐", + "name": "neutral face" + }, + { + "emoji": "😑", + "name": "expressionless face" + }, + { + "emoji": "😶", + "name": "face without mouth" + }, + { + "emoji": "🫥", + "name": "dotted line face" + }, + { + "emoji": "😶‍🌫️", + "name": "face in clouds" + }, + { + "emoji": "😏", + "name": "smirking face" + }, + { + "emoji": "😒", + "name": "unamused face" + }, + { + "emoji": "🙄", + "name": "face with rolling eyes" + }, + { + "emoji": "😬", + "name": "grimacing face" + }, + { + "emoji": "😮‍💨", + "name": "face exhaling" + }, + { + "emoji": "🤥", + "name": "lying face" + }, + { + "emoji": "🫨", + "name": "shaking face" + }, + { + "emoji": "🙂‍↔️", + "name": "head shaking horizontally" + }, + { + "emoji": "🙂‍↕️", + "name": "head shaking vertically" + }, + { + "emoji": "😌", + "name": "relieved face" + }, + { + "emoji": "😔", + "name": "pensive face" + }, + { + "emoji": "😪", + "name": "sleepy face" + }, + { + "emoji": "🤤", + "name": "drooling face" + }, + { + "emoji": "😴", + "name": "sleeping face" + }, + { + "emoji": "😷", + "name": "face with medical mask" + }, + { + "emoji": "🤒", + "name": "face with thermometer" + }, + { + "emoji": "🤕", + "name": "face with head-bandage" + }, + { + "emoji": "🤢", + "name": "nauseated face" + }, + { + "emoji": "🤮", + "name": "face vomiting" + }, + { + "emoji": "🤧", + "name": "sneezing face" + }, + { + "emoji": "🥵", + "name": "hot face" + }, + { + "emoji": "🥶", + "name": "cold face" + }, + { + "emoji": "🥴", + "name": "woozy face" + }, + { + "emoji": "😵", + "name": "face with crossed-out eyes" + }, + { + "emoji": "😵‍💫", + "name": "face with spiral eyes" + }, + { + "emoji": "🤯", + "name": "exploding head" + }, + { + "emoji": "🤠", + "name": "cowboy hat face" + }, + { + "emoji": "🥳", + "name": "partying face" + }, + { + "emoji": "🥸", + "name": "disguised face" + }, + { + "emoji": "😎", + "name": "smiling face with sunglasses" + }, + { + "emoji": "🤓", + "name": "nerd face" + }, + { + "emoji": "🧐", + "name": "face with monocle" + }, + { + "emoji": "😕", + "name": "confused face" + }, + { + "emoji": "🫤", + "name": "face with diagonal mouth" + }, + { + "emoji": "😟", + "name": "worried face" + }, + { + "emoji": "🙁", + "name": "slightly frowning face" + }, + { + "emoji": "☹️", + "name": "frowning face" + }, + { + "emoji": "😮", + "name": "face with open mouth" + }, + { + "emoji": "😯", + "name": "hushed face" + }, + { + "emoji": "😲", + "name": "astonished face" + }, + { + "emoji": "😳", + "name": "flushed face" + }, + { + "emoji": "🥺", + "name": "pleading face" + }, + { + "emoji": "🥹", + "name": "face holding back tears" + }, + { + "emoji": "😦", + "name": "frowning face with open mouth" + }, + { + "emoji": "😧", + "name": "anguished face" + }, + { + "emoji": "😨", + "name": "fearful face" + }, + { + "emoji": "😰", + "name": "anxious face with sweat" + }, + { + "emoji": "😥", + "name": "sad but relieved face" + }, + { + "emoji": "😢", + "name": "crying face" + }, + { + "emoji": "😭", + "name": "loudly crying face" + }, + { + "emoji": "😱", + "name": "face screaming in fear" + }, + { + "emoji": "😖", + "name": "confounded face" + }, + { + "emoji": "😣", + "name": "persevering face" + }, + { + "emoji": "😞", + "name": "disappointed face" + }, + { + "emoji": "😓", + "name": "downcast face with sweat" + }, + { + "emoji": "😩", + "name": "weary face" + }, + { + "emoji": "😫", + "name": "tired face" + }, + { + "emoji": "🥱", + "name": "yawning face" + }, + { + "emoji": "😤", + "name": "face with steam from nose" + }, + { + "emoji": "😡", + "name": "enraged face" + }, + { + "emoji": "😠", + "name": "angry face" + }, + { + "emoji": "🤬", + "name": "face with symbols on mouth" + }, + { + "emoji": "😈", + "name": "smiling face with horns" + }, + { + "emoji": "👿", + "name": "angry face with horns" + }, + { + "emoji": "💀", + "name": "skull" + }, + { + "emoji": "☠️", + "name": "skull and crossbones" + }, + { + "emoji": "💩", + "name": "pile of poo" + }, + { + "emoji": "🤡", + "name": "clown face" + }, + { + "emoji": "👹", + "name": "ogre" + }, + { + "emoji": "👺", + "name": "goblin" + }, + { + "emoji": "👻", + "name": "ghost" + }, + { + "emoji": "👽", + "name": "alien" + }, + { + "emoji": "👾", + "name": "alien monster" + }, + { + "emoji": "🤖", + "name": "robot" + }, + { + "emoji": "😺", + "name": "grinning cat" + }, + { + "emoji": "😸", + "name": "grinning cat with smiling eyes" + }, + { + "emoji": "😹", + "name": "cat with tears of joy" + }, + { + "emoji": "😻", + "name": "smiling cat with heart-eyes" + }, + { + "emoji": "😼", + "name": "cat with wry smile" + }, + { + "emoji": "😽", + "name": "kissing cat" + }, + { + "emoji": "🙀", + "name": "weary cat" + }, + { + "emoji": "😿", + "name": "crying cat" + }, + { + "emoji": "😾", + "name": "pouting cat" + }, + { + "emoji": "🙈", + "name": "see-no-evil monkey" + }, + { + "emoji": "🙉", + "name": "hear-no-evil monkey" + }, + { + "emoji": "🙊", + "name": "speak-no-evil monkey" + }, + { + "emoji": "💌", + "name": "love letter" + }, + { + "emoji": "💘", + "name": "heart with arrow" + }, + { + "emoji": "💝", + "name": "heart with ribbon" + }, + { + "emoji": "💖", + "name": "sparkling heart" + }, + { + "emoji": "💗", + "name": "growing heart" + }, + { + "emoji": "💓", + "name": "beating heart" + }, + { + "emoji": "💞", + "name": "revolving hearts" + }, + { + "emoji": "💕", + "name": "two hearts" + }, + { + "emoji": "💟", + "name": "heart decoration" + }, + { + "emoji": "❣️", + "name": "heart exclamation" + }, + { + "emoji": "💔", + "name": "broken heart" + }, + { + "emoji": "❤️‍🔥", + "name": "heart on fire" + }, + { + "emoji": "❤️‍🩹", + "name": "mending heart" + }, + { + "emoji": "❤️", + "name": "red heart" + }, + { + "emoji": "🩷", + "name": "pink heart" + }, + { + "emoji": "🧡", + "name": "orange heart" + }, + { + "emoji": "💛", + "name": "yellow heart" + }, + { + "emoji": "💚", + "name": "green heart" + }, + { + "emoji": "💙", + "name": "blue heart" + }, + { + "emoji": "🩵", + "name": "light blue heart" + }, + { + "emoji": "💜", + "name": "purple heart" + }, + { + "emoji": "🤎", + "name": "brown heart" + }, + { + "emoji": "🖤", + "name": "black heart" + }, + { + "emoji": "🩶", + "name": "grey heart" + }, + { + "emoji": "🤍", + "name": "white heart" + }, + { + "emoji": "💋", + "name": "kiss mark" + }, + { + "emoji": "💯", + "name": "hundred points" + }, + { + "emoji": "💢", + "name": "anger symbol" + }, + { + "emoji": "💥", + "name": "collision" + }, + { + "emoji": "💫", + "name": "dizzy" + }, + { + "emoji": "💦", + "name": "sweat droplets" + }, + { + "emoji": "💨", + "name": "dashing away" + }, + { + "emoji": "🕳️", + "name": "hole" + }, + { + "emoji": "💬", + "name": "speech balloon" + }, + { + "emoji": "👁️‍🗨️", + "name": "eye in speech bubble" + }, + { + "emoji": "🗨️", + "name": "left speech bubble" + }, + { + "emoji": "🗯️", + "name": "right anger bubble" + }, + { + "emoji": "💭", + "name": "thought balloon" + }, + { + "emoji": "💤", + "name": "ZZZ" + } + ], + "People & Body": [ + { + "emoji": "👋", + "name": "waving hand" + }, + { + "emoji": "🤚", + "name": "raised back of hand" + }, + { + "emoji": "🖐️", + "name": "hand with fingers splayed" + }, + { + "emoji": "✋", + "name": "raised hand" + }, + { + "emoji": "🖖", + "name": "vulcan salute" + }, + { + "emoji": "🫱", + "name": "rightwards hand" + }, + { + "emoji": "🫲", + "name": "leftwards hand" + }, + { + "emoji": "🫳", + "name": "palm down hand" + }, + { + "emoji": "🫴", + "name": "palm up hand" + }, + { + "emoji": "🫷", + "name": "leftwards pushing hand" + }, + { + "emoji": "🫸", + "name": "rightwards pushing hand" + }, + { + "emoji": "👌", + "name": "OK hand" + }, + { + "emoji": "🤌", + "name": "pinched fingers" + }, + { + "emoji": "🤏", + "name": "pinching hand" + }, + { + "emoji": "✌️", + "name": "victory hand" + }, + { + "emoji": "🤞", + "name": "crossed fingers" + }, + { + "emoji": "🫰", + "name": "hand with index finger and thumb crossed" + }, + { + "emoji": "🤟", + "name": "love-you gesture" + }, + { + "emoji": "🤘", + "name": "sign of the horns" + }, + { + "emoji": "🤙", + "name": "call me hand" + }, + { + "emoji": "👈", + "name": "backhand index pointing left" + }, + { + "emoji": "👉", + "name": "backhand index pointing right" + }, + { + "emoji": "👆", + "name": "backhand index pointing up" + }, + { + "emoji": "🖕", + "name": "middle finger" + }, + { + "emoji": "👇", + "name": "backhand index pointing down" + }, + { + "emoji": "☝️", + "name": "index pointing up" + }, + { + "emoji": "🫵", + "name": "index pointing at the viewer" + }, + { + "emoji": "👍", + "name": "thumbs up" + }, + { + "emoji": "👎", + "name": "thumbs down" + }, + { + "emoji": "✊", + "name": "raised fist" + }, + { + "emoji": "👊", + "name": "oncoming fist" + }, + { + "emoji": "🤛", + "name": "left-facing fist" + }, + { + "emoji": "🤜", + "name": "right-facing fist" + }, + { + "emoji": "👏", + "name": "clapping hands" + }, + { + "emoji": "🙌", + "name": "raising hands" + }, + { + "emoji": "🫶", + "name": "heart hands" + }, + { + "emoji": "👐", + "name": "open hands" + }, + { + "emoji": "🤲", + "name": "palms up together" + }, + { + "emoji": "🤝", + "name": "handshake" + }, + { + "emoji": "🙏", + "name": "folded hands" + }, + { + "emoji": "✍️", + "name": "writing hand" + }, + { + "emoji": "💅", + "name": "nail polish" + }, + { + "emoji": "🤳", + "name": "selfie" + }, + { + "emoji": "💪", + "name": "flexed biceps" + }, + { + "emoji": "🦾", + "name": "mechanical arm" + }, + { + "emoji": "🦿", + "name": "mechanical leg" + }, + { + "emoji": "🦵", + "name": "leg" + }, + { + "emoji": "🦶", + "name": "foot" + }, + { + "emoji": "👂", + "name": "ear" + }, + { + "emoji": "🦻", + "name": "ear with hearing aid" + }, + { + "emoji": "👃", + "name": "nose" + }, + { + "emoji": "🧠", + "name": "brain" + }, + { + "emoji": "🫀", + "name": "anatomical heart" + }, + { + "emoji": "🫁", + "name": "lungs" + }, + { + "emoji": "🦷", + "name": "tooth" + }, + { + "emoji": "🦴", + "name": "bone" + }, + { + "emoji": "👀", + "name": "eyes" + }, + { + "emoji": "👁️", + "name": "eye" + }, + { + "emoji": "👅", + "name": "tongue" + }, + { + "emoji": "👄", + "name": "mouth" + }, + { + "emoji": "🫦", + "name": "biting lip" + }, + { + "emoji": "👶", + "name": "baby" + }, + { + "emoji": "🧒", + "name": "child" + }, + { + "emoji": "👦", + "name": "boy" + }, + { + "emoji": "👧", + "name": "girl" + }, + { + "emoji": "🧑", + "name": "person" + }, + { + "emoji": "👱", + "name": "person blond hair" + }, + { + "emoji": "👨", + "name": "man" + }, + { + "emoji": "🧔", + "name": "person beard" + }, + { + "emoji": "🧔‍♂️", + "name": "man beard" + }, + { + "emoji": "🧔‍♀️", + "name": "woman beard" + }, + { + "emoji": "👨‍🦰", + "name": "man red hair" + }, + { + "emoji": "👨‍🦱", + "name": "man curly hair" + }, + { + "emoji": "👨‍🦳", + "name": "man white hair" + }, + { + "emoji": "👨‍🦲", + "name": "man bald" + }, + { + "emoji": "👩", + "name": "woman" + }, + { + "emoji": "👩‍🦰", + "name": "woman red hair" + }, + { + "emoji": "🧑‍🦰", + "name": "person red hair" + }, + { + "emoji": "👩‍🦱", + "name": "woman curly hair" + }, + { + "emoji": "🧑‍🦱", + "name": "person curly hair" + }, + { + "emoji": "👩‍🦳", + "name": "woman white hair" + }, + { + "emoji": "🧑‍🦳", + "name": "person white hair" + }, + { + "emoji": "👩‍🦲", + "name": "woman bald" + }, + { + "emoji": "🧑‍🦲", + "name": "person bald" + }, + { + "emoji": "👱‍♀️", + "name": "woman blond hair" + }, + { + "emoji": "👱‍♂️", + "name": "man blond hair" + }, + { + "emoji": "🧓", + "name": "older person" + }, + { + "emoji": "👴", + "name": "old man" + }, + { + "emoji": "👵", + "name": "old woman" + }, + { + "emoji": "🙍", + "name": "person frowning" + }, + { + "emoji": "🙍‍♂️", + "name": "man frowning" + }, + { + "emoji": "🙍‍♀️", + "name": "woman frowning" + }, + { + "emoji": "🙎", + "name": "person pouting" + }, + { + "emoji": "🙎‍♂️", + "name": "man pouting" + }, + { + "emoji": "🙎‍♀️", + "name": "woman pouting" + }, + { + "emoji": "🙅", + "name": "person gesturing NO" + }, + { + "emoji": "🙅‍♂️", + "name": "man gesturing NO" + }, + { + "emoji": "🙅‍♀️", + "name": "woman gesturing NO" + }, + { + "emoji": "🙆", + "name": "person gesturing OK" + }, + { + "emoji": "🙆‍♂️", + "name": "man gesturing OK" + }, + { + "emoji": "🙆‍♀️", + "name": "woman gesturing OK" + }, + { + "emoji": "💁", + "name": "person tipping hand" + }, + { + "emoji": "💁‍♂️", + "name": "man tipping hand" + }, + { + "emoji": "💁‍♀️", + "name": "woman tipping hand" + }, + { + "emoji": "🙋", + "name": "person raising hand" + }, + { + "emoji": "🙋‍♂️", + "name": "man raising hand" + }, + { + "emoji": "🙋‍♀️", + "name": "woman raising hand" + }, + { + "emoji": "🧏", + "name": "deaf person" + }, + { + "emoji": "🧏‍♂️", + "name": "deaf man" + }, + { + "emoji": "🧏‍♀️", + "name": "deaf woman" + }, + { + "emoji": "🙇", + "name": "person bowing" + }, + { + "emoji": "🙇‍♂️", + "name": "man bowing" + }, + { + "emoji": "🙇‍♀️", + "name": "woman bowing" + }, + { + "emoji": "🤦", + "name": "person facepalming" + }, + { + "emoji": "🤦‍♂️", + "name": "man facepalming" + }, + { + "emoji": "🤦‍♀️", + "name": "woman facepalming" + }, + { + "emoji": "🤷", + "name": "person shrugging" + }, + { + "emoji": "🤷‍♂️", + "name": "man shrugging" + }, + { + "emoji": "🤷‍♀️", + "name": "woman shrugging" + }, + { + "emoji": "🧑‍⚕️", + "name": "health worker" + }, + { + "emoji": "👨‍⚕️", + "name": "man health worker" + }, + { + "emoji": "👩‍⚕️", + "name": "woman health worker" + }, + { + "emoji": "🧑‍🎓", + "name": "student" + }, + { + "emoji": "👨‍🎓", + "name": "man student" + }, + { + "emoji": "👩‍🎓", + "name": "woman student" + }, + { + "emoji": "🧑‍🏫", + "name": "teacher" + }, + { + "emoji": "👨‍🏫", + "name": "man teacher" + }, + { + "emoji": "👩‍🏫", + "name": "woman teacher" + }, + { + "emoji": "🧑‍⚖️", + "name": "judge" + }, + { + "emoji": "👨‍⚖️", + "name": "man judge" + }, + { + "emoji": "👩‍⚖️", + "name": "woman judge" + }, + { + "emoji": "🧑‍🌾", + "name": "farmer" + }, + { + "emoji": "👨‍🌾", + "name": "man farmer" + }, + { + "emoji": "👩‍🌾", + "name": "woman farmer" + }, + { + "emoji": "🧑‍🍳", + "name": "cook" + }, + { + "emoji": "👨‍🍳", + "name": "man cook" + }, + { + "emoji": "👩‍🍳", + "name": "woman cook" + }, + { + "emoji": "🧑‍🔧", + "name": "mechanic" + }, + { + "emoji": "👨‍🔧", + "name": "man mechanic" + }, + { + "emoji": "👩‍🔧", + "name": "woman mechanic" + }, + { + "emoji": "🧑‍🏭", + "name": "factory worker" + }, + { + "emoji": "👨‍🏭", + "name": "man factory worker" + }, + { + "emoji": "👩‍🏭", + "name": "woman factory worker" + }, + { + "emoji": "🧑‍💼", + "name": "office worker" + }, + { + "emoji": "👨‍💼", + "name": "man office worker" + }, + { + "emoji": "👩‍💼", + "name": "woman office worker" + }, + { + "emoji": "🧑‍🔬", + "name": "scientist" + }, + { + "emoji": "👨‍🔬", + "name": "man scientist" + }, + { + "emoji": "👩‍🔬", + "name": "woman scientist" + }, + { + "emoji": "🧑‍💻", + "name": "technologist" + }, + { + "emoji": "👨‍💻", + "name": "man technologist" + }, + { + "emoji": "👩‍💻", + "name": "woman technologist" + }, + { + "emoji": "🧑‍🎤", + "name": "singer" + }, + { + "emoji": "👨‍🎤", + "name": "man singer" + }, + { + "emoji": "👩‍🎤", + "name": "woman singer" + }, + { + "emoji": "🧑‍🎨", + "name": "artist" + }, + { + "emoji": "👨‍🎨", + "name": "man artist" + }, + { + "emoji": "👩‍🎨", + "name": "woman artist" + }, + { + "emoji": "🧑‍✈️", + "name": "pilot" + }, + { + "emoji": "👨‍✈️", + "name": "man pilot" + }, + { + "emoji": "👩‍✈️", + "name": "woman pilot" + }, + { + "emoji": "🧑‍🚀", + "name": "astronaut" + }, + { + "emoji": "👨‍🚀", + "name": "man astronaut" + }, + { + "emoji": "👩‍🚀", + "name": "woman astronaut" + }, + { + "emoji": "🧑‍🚒", + "name": "firefighter" + }, + { + "emoji": "👨‍🚒", + "name": "man firefighter" + }, + { + "emoji": "👩‍🚒", + "name": "woman firefighter" + }, + { + "emoji": "👮", + "name": "police officer" + }, + { + "emoji": "👮‍♂️", + "name": "man police officer" + }, + { + "emoji": "👮‍♀️", + "name": "woman police officer" + }, + { + "emoji": "🕵️", + "name": "detective" + }, + { + "emoji": "🕵️‍♂️", + "name": "man detective" + }, + { + "emoji": "🕵️‍♀️", + "name": "woman detective" + }, + { + "emoji": "💂", + "name": "guard" + }, + { + "emoji": "💂‍♂️", + "name": "man guard" + }, + { + "emoji": "💂‍♀️", + "name": "woman guard" + }, + { + "emoji": "🥷", + "name": "ninja" + }, + { + "emoji": "👷", + "name": "construction worker" + }, + { + "emoji": "👷‍♂️", + "name": "man construction worker" + }, + { + "emoji": "👷‍♀️", + "name": "woman construction worker" + }, + { + "emoji": "🫅", + "name": "person with crown" + }, + { + "emoji": "🤴", + "name": "prince" + }, + { + "emoji": "👸", + "name": "princess" + }, + { + "emoji": "👳", + "name": "person wearing turban" + }, + { + "emoji": "👳‍♂️", + "name": "man wearing turban" + }, + { + "emoji": "👳‍♀️", + "name": "woman wearing turban" + }, + { + "emoji": "👲", + "name": "person with skullcap" + }, + { + "emoji": "🧕", + "name": "woman with headscarf" + }, + { + "emoji": "🤵", + "name": "person in tuxedo" + }, + { + "emoji": "🤵‍♂️", + "name": "man in tuxedo" + }, + { + "emoji": "🤵‍♀️", + "name": "woman in tuxedo" + }, + { + "emoji": "👰", + "name": "person with veil" + }, + { + "emoji": "👰‍♂️", + "name": "man with veil" + }, + { + "emoji": "👰‍♀️", + "name": "woman with veil" + }, + { + "emoji": "🤰", + "name": "pregnant woman" + }, + { + "emoji": "🫃", + "name": "pregnant man" + }, + { + "emoji": "🫄", + "name": "pregnant person" + }, + { + "emoji": "🤱", + "name": "breast-feeding" + }, + { + "emoji": "👩‍🍼", + "name": "woman feeding baby" + }, + { + "emoji": "👨‍🍼", + "name": "man feeding baby" + }, + { + "emoji": "🧑‍🍼", + "name": "person feeding baby" + }, + { + "emoji": "👼", + "name": "baby angel" + }, + { + "emoji": "🎅", + "name": "Santa Claus" + }, + { + "emoji": "🤶", + "name": "Mrs. Claus" + }, + { + "emoji": "🧑‍🎄", + "name": "mx claus" + }, + { + "emoji": "🦸", + "name": "superhero" + }, + { + "emoji": "🦸‍♂️", + "name": "man superhero" + }, + { + "emoji": "🦸‍♀️", + "name": "woman superhero" + }, + { + "emoji": "🦹", + "name": "supervillain" + }, + { + "emoji": "🦹‍♂️", + "name": "man supervillain" + }, + { + "emoji": "🦹‍♀️", + "name": "woman supervillain" + }, + { + "emoji": "🧙", + "name": "mage" + }, + { + "emoji": "🧙‍♂️", + "name": "man mage" + }, + { + "emoji": "🧙‍♀️", + "name": "woman mage" + }, + { + "emoji": "🧚", + "name": "fairy" + }, + { + "emoji": "🧚‍♂️", + "name": "man fairy" + }, + { + "emoji": "🧚‍♀️", + "name": "woman fairy" + }, + { + "emoji": "🧛", + "name": "vampire" + }, + { + "emoji": "🧛‍♂️", + "name": "man vampire" + }, + { + "emoji": "🧛‍♀️", + "name": "woman vampire" + }, + { + "emoji": "🧜", + "name": "merperson" + }, + { + "emoji": "🧜‍♂️", + "name": "merman" + }, + { + "emoji": "🧜‍♀️", + "name": "mermaid" + }, + { + "emoji": "🧝", + "name": "elf" + }, + { + "emoji": "🧝‍♂️", + "name": "man elf" + }, + { + "emoji": "🧝‍♀️", + "name": "woman elf" + }, + { + "emoji": "🧞", + "name": "genie" + }, + { + "emoji": "🧞‍♂️", + "name": "man genie" + }, + { + "emoji": "🧞‍♀️", + "name": "woman genie" + }, + { + "emoji": "🧟", + "name": "zombie" + }, + { + "emoji": "🧟‍♂️", + "name": "man zombie" + }, + { + "emoji": "🧟‍♀️", + "name": "woman zombie" + }, + { + "emoji": "🧌", + "name": "troll" + }, + { + "emoji": "💆", + "name": "person getting massage" + }, + { + "emoji": "💆‍♂️", + "name": "man getting massage" + }, + { + "emoji": "💆‍♀️", + "name": "woman getting massage" + }, + { + "emoji": "💇", + "name": "person getting haircut" + }, + { + "emoji": "💇‍♂️", + "name": "man getting haircut" + }, + { + "emoji": "💇‍♀️", + "name": "woman getting haircut" + }, + { + "emoji": "🚶", + "name": "person walking" + }, + { + "emoji": "🚶‍♂️", + "name": "man walking" + }, + { + "emoji": "🚶‍♀️", + "name": "woman walking" + }, + { + "emoji": "🚶‍➡️", + "name": "person walking facing right" + }, + { + "emoji": "🚶‍♀️‍➡️", + "name": "woman walking facing right" + }, + { + "emoji": "🚶‍♂️‍➡️", + "name": "man walking facing right" + }, + { + "emoji": "🧍", + "name": "person standing" + }, + { + "emoji": "🧍‍♂️", + "name": "man standing" + }, + { + "emoji": "🧍‍♀️", + "name": "woman standing" + }, + { + "emoji": "🧎", + "name": "person kneeling" + }, + { + "emoji": "🧎‍♂️", + "name": "man kneeling" + }, + { + "emoji": "🧎‍♀️", + "name": "woman kneeling" + }, + { + "emoji": "🧎‍➡️", + "name": "person kneeling facing right" + }, + { + "emoji": "🧎‍♀️‍➡️", + "name": "woman kneeling facing right" + }, + { + "emoji": "🧎‍♂️‍➡️", + "name": "man kneeling facing right" + }, + { + "emoji": "🧑‍🦯", + "name": "person with white cane" + }, + { + "emoji": "🧑‍🦯‍➡️", + "name": "person with white cane facing right" + }, + { + "emoji": "👨‍🦯", + "name": "man with white cane" + }, + { + "emoji": "👨‍🦯‍➡️", + "name": "man with white cane facing right" + }, + { + "emoji": "👩‍🦯", + "name": "woman with white cane" + }, + { + "emoji": "👩‍🦯‍➡️", + "name": "woman with white cane facing right" + }, + { + "emoji": "🧑‍🦼", + "name": "person in motorized wheelchair" + }, + { + "emoji": "🧑‍🦼‍➡️", + "name": "person in motorized wheelchair facing right" + }, + { + "emoji": "👨‍🦼", + "name": "man in motorized wheelchair" + }, + { + "emoji": "👨‍🦼‍➡️", + "name": "man in motorized wheelchair facing right" + }, + { + "emoji": "👩‍🦼", + "name": "woman in motorized wheelchair" + }, + { + "emoji": "👩‍🦼‍➡️", + "name": "woman in motorized wheelchair facing right" + }, + { + "emoji": "🧑‍🦽", + "name": "person in manual wheelchair" + }, + { + "emoji": "🧑‍🦽‍➡️", + "name": "person in manual wheelchair facing right" + }, + { + "emoji": "👨‍🦽", + "name": "man in manual wheelchair" + }, + { + "emoji": "👨‍🦽‍➡️", + "name": "man in manual wheelchair facing right" + }, + { + "emoji": "👩‍🦽", + "name": "woman in manual wheelchair" + }, + { + "emoji": "👩‍🦽‍➡️", + "name": "woman in manual wheelchair facing right" + }, + { + "emoji": "🏃", + "name": "person running" + }, + { + "emoji": "🏃‍♂️", + "name": "man running" + }, + { + "emoji": "🏃‍♀️", + "name": "woman running" + }, + { + "emoji": "🏃‍➡️", + "name": "person running facing right" + }, + { + "emoji": "🏃‍♀️‍➡️", + "name": "woman running facing right" + }, + { + "emoji": "🏃‍♂️‍➡️", + "name": "man running facing right" + }, + { + "emoji": "💃", + "name": "woman dancing" + }, + { + "emoji": "🕺", + "name": "man dancing" + }, + { + "emoji": "🕴️", + "name": "person in suit levitating" + }, + { + "emoji": "👯", + "name": "people with bunny ears" + }, + { + "emoji": "👯‍♂️", + "name": "men with bunny ears" + }, + { + "emoji": "👯‍♀️", + "name": "women with bunny ears" + }, + { + "emoji": "🧖", + "name": "person in steamy room" + }, + { + "emoji": "🧖‍♂️", + "name": "man in steamy room" + }, + { + "emoji": "🧖‍♀️", + "name": "woman in steamy room" + }, + { + "emoji": "🧗", + "name": "person climbing" + }, + { + "emoji": "🧗‍♂️", + "name": "man climbing" + }, + { + "emoji": "🧗‍♀️", + "name": "woman climbing" + }, + { + "emoji": "🤺", + "name": "person fencing" + }, + { + "emoji": "🏇", + "name": "horse racing" + }, + { + "emoji": "⛷️", + "name": "skier" + }, + { + "emoji": "🏂", + "name": "snowboarder" + }, + { + "emoji": "🏌️", + "name": "person golfing" + }, + { + "emoji": "🏌️‍♂️", + "name": "man golfing" + }, + { + "emoji": "🏌️‍♀️", + "name": "woman golfing" + }, + { + "emoji": "🏄", + "name": "person surfing" + }, + { + "emoji": "🏄‍♂️", + "name": "man surfing" + }, + { + "emoji": "🏄‍♀️", + "name": "woman surfing" + }, + { + "emoji": "🚣", + "name": "person rowing boat" + }, + { + "emoji": "🚣‍♂️", + "name": "man rowing boat" + }, + { + "emoji": "🚣‍♀️", + "name": "woman rowing boat" + }, + { + "emoji": "🏊", + "name": "person swimming" + }, + { + "emoji": "🏊‍♂️", + "name": "man swimming" + }, + { + "emoji": "🏊‍♀️", + "name": "woman swimming" + }, + { + "emoji": "⛹️", + "name": "person bouncing ball" + }, + { + "emoji": "⛹️‍♂️", + "name": "man bouncing ball" + }, + { + "emoji": "⛹️‍♀️", + "name": "woman bouncing ball" + }, + { + "emoji": "🏋️", + "name": "person lifting weights" + }, + { + "emoji": "🏋️‍♂️", + "name": "man lifting weights" + }, + { + "emoji": "🏋️‍♀️", + "name": "woman lifting weights" + }, + { + "emoji": "🚴", + "name": "person biking" + }, + { + "emoji": "🚴‍♂️", + "name": "man biking" + }, + { + "emoji": "🚴‍♀️", + "name": "woman biking" + }, + { + "emoji": "🚵", + "name": "person mountain biking" + }, + { + "emoji": "🚵‍♂️", + "name": "man mountain biking" + }, + { + "emoji": "🚵‍♀️", + "name": "woman mountain biking" + }, + { + "emoji": "🤸", + "name": "person cartwheeling" + }, + { + "emoji": "🤸‍♂️", + "name": "man cartwheeling" + }, + { + "emoji": "🤸‍♀️", + "name": "woman cartwheeling" + }, + { + "emoji": "🤼", + "name": "people wrestling" + }, + { + "emoji": "🤼‍♂️", + "name": "men wrestling" + }, + { + "emoji": "🤼‍♀️", + "name": "women wrestling" + }, + { + "emoji": "🤽", + "name": "person playing water polo" + }, + { + "emoji": "🤽‍♂️", + "name": "man playing water polo" + }, + { + "emoji": "🤽‍♀️", + "name": "woman playing water polo" + }, + { + "emoji": "🤾", + "name": "person playing handball" + }, + { + "emoji": "🤾‍♂️", + "name": "man playing handball" + }, + { + "emoji": "🤾‍♀️", + "name": "woman playing handball" + }, + { + "emoji": "🤹", + "name": "person juggling" + }, + { + "emoji": "🤹‍♂️", + "name": "man juggling" + }, + { + "emoji": "🤹‍♀️", + "name": "woman juggling" + }, + { + "emoji": "🧘", + "name": "person in lotus position" + }, + { + "emoji": "🧘‍♂️", + "name": "man in lotus position" + }, + { + "emoji": "🧘‍♀️", + "name": "woman in lotus position" + }, + { + "emoji": "🛀", + "name": "person taking bath" + }, + { + "emoji": "🛌", + "name": "person in bed" + }, + { + "emoji": "🧑‍🤝‍🧑", + "name": "people holding hands" + }, + { + "emoji": "👭", + "name": "women holding hands" + }, + { + "emoji": "👫", + "name": "woman and man holding hands" + }, + { + "emoji": "👬", + "name": "men holding hands" + }, + { + "emoji": "💏", + "name": "kiss" + }, + { + "emoji": "👩‍❤️‍💋‍👨", + "name": "kiss woman, man" + }, + { + "emoji": "👨‍❤️‍💋‍👨", + "name": "kiss man, man" + }, + { + "emoji": "👩‍❤️‍💋‍👩", + "name": "kiss woman, woman" + }, + { + "emoji": "💑", + "name": "couple with heart" + }, + { + "emoji": "👩‍❤️‍👨", + "name": "couple with heart woman, man" + }, + { + "emoji": "👨‍❤️‍👨", + "name": "couple with heart man, man" + }, + { + "emoji": "👩‍❤️‍👩", + "name": "couple with heart woman, woman" + }, + { + "emoji": "👨‍👩‍👦", + "name": "family man, woman, boy" + }, + { + "emoji": "👨‍👩‍👧", + "name": "family man, woman, girl" + }, + { + "emoji": "👨‍👩‍👧‍👦", + "name": "family man, woman, girl, boy" + }, + { + "emoji": "👨‍👩‍👦‍👦", + "name": "family man, woman, boy, boy" + }, + { + "emoji": "👨‍👩‍👧‍👧", + "name": "family man, woman, girl, girl" + }, + { + "emoji": "👨‍👨‍👦", + "name": "family man, man, boy" + }, + { + "emoji": "👨‍👨‍👧", + "name": "family man, man, girl" + }, + { + "emoji": "👨‍👨‍👧‍👦", + "name": "family man, man, girl, boy" + }, + { + "emoji": "👨‍👨‍👦‍👦", + "name": "family man, man, boy, boy" + }, + { + "emoji": "👨‍👨‍👧‍👧", + "name": "family man, man, girl, girl" + }, + { + "emoji": "👩‍👩‍👦", + "name": "family woman, woman, boy" + }, + { + "emoji": "👩‍👩‍👧", + "name": "family woman, woman, girl" + }, + { + "emoji": "👩‍👩‍👧‍👦", + "name": "family woman, woman, girl, boy" + }, + { + "emoji": "👩‍👩‍👦‍👦", + "name": "family woman, woman, boy, boy" + }, + { + "emoji": "👩‍👩‍👧‍👧", + "name": "family woman, woman, girl, girl" + }, + { + "emoji": "👨‍👦", + "name": "family man, boy" + }, + { + "emoji": "👨‍👦‍👦", + "name": "family man, boy, boy" + }, + { + "emoji": "👨‍👧", + "name": "family man, girl" + }, + { + "emoji": "👨‍👧‍👦", + "name": "family man, girl, boy" + }, + { + "emoji": "👨‍👧‍👧", + "name": "family man, girl, girl" + }, + { + "emoji": "👩‍👦", + "name": "family woman, boy" + }, + { + "emoji": "👩‍👦‍👦", + "name": "family woman, boy, boy" + }, + { + "emoji": "👩‍👧", + "name": "family woman, girl" + }, + { + "emoji": "👩‍👧‍👦", + "name": "family woman, girl, boy" + }, + { + "emoji": "👩‍👧‍👧", + "name": "family woman, girl, girl" + }, + { + "emoji": "🗣️", + "name": "speaking head" + }, + { + "emoji": "👤", + "name": "bust in silhouette" + }, + { + "emoji": "👥", + "name": "busts in silhouette" + }, + { + "emoji": "🫂", + "name": "people hugging" + }, + { + "emoji": "👪", + "name": "family" + }, + { + "emoji": "🧑‍🧑‍🧒", + "name": "family adult, adult, child" + }, + { + "emoji": "🧑‍🧑‍🧒‍🧒", + "name": "family adult, adult, child, child" + }, + { + "emoji": "🧑‍🧒", + "name": "family adult, child" + }, + { + "emoji": "🧑‍🧒‍🧒", + "name": "family adult, child, child" + }, + { + "emoji": "👣", + "name": "footprints" + } + ], + "Animals & Nature": [ + { + "emoji": "🐵", + "name": "monkey face" + }, + { + "emoji": "🐒", + "name": "monkey" + }, + { + "emoji": "🦍", + "name": "gorilla" + }, + { + "emoji": "🦧", + "name": "orangutan" + }, + { + "emoji": "🐶", + "name": "dog face" + }, + { + "emoji": "🐕", + "name": "dog" + }, + { + "emoji": "🦮", + "name": "guide dog" + }, + { + "emoji": "🐕‍🦺", + "name": "service dog" + }, + { + "emoji": "🐩", + "name": "poodle" + }, + { + "emoji": "🐺", + "name": "wolf" + }, + { + "emoji": "🦊", + "name": "fox" + }, + { + "emoji": "🦝", + "name": "raccoon" + }, + { + "emoji": "🐱", + "name": "cat face" + }, + { + "emoji": "🐈", + "name": "cat" + }, + { + "emoji": "🐈‍⬛", + "name": "black cat" + }, + { + "emoji": "🦁", + "name": "lion" + }, + { + "emoji": "🐯", + "name": "tiger face" + }, + { + "emoji": "🐅", + "name": "tiger" + }, + { + "emoji": "🐆", + "name": "leopard" + }, + { + "emoji": "🐴", + "name": "horse face" + }, + { + "emoji": "🫎", + "name": "moose" + }, + { + "emoji": "🫏", + "name": "donkey" + }, + { + "emoji": "🐎", + "name": "horse" + }, + { + "emoji": "🦄", + "name": "unicorn" + }, + { + "emoji": "🦓", + "name": "zebra" + }, + { + "emoji": "🦌", + "name": "deer" + }, + { + "emoji": "🦬", + "name": "bison" + }, + { + "emoji": "🐮", + "name": "cow face" + }, + { + "emoji": "🐂", + "name": "ox" + }, + { + "emoji": "🐃", + "name": "water buffalo" + }, + { + "emoji": "🐄", + "name": "cow" + }, + { + "emoji": "🐷", + "name": "pig face" + }, + { + "emoji": "🐖", + "name": "pig" + }, + { + "emoji": "🐗", + "name": "boar" + }, + { + "emoji": "🐽", + "name": "pig nose" + }, + { + "emoji": "🐏", + "name": "ram" + }, + { + "emoji": "🐑", + "name": "ewe" + }, + { + "emoji": "🐐", + "name": "goat" + }, + { + "emoji": "🐪", + "name": "camel" + }, + { + "emoji": "🐫", + "name": "two-hump camel" + }, + { + "emoji": "🦙", + "name": "llama" + }, + { + "emoji": "🦒", + "name": "giraffe" + }, + { + "emoji": "🐘", + "name": "elephant" + }, + { + "emoji": "🦣", + "name": "mammoth" + }, + { + "emoji": "🦏", + "name": "rhinoceros" + }, + { + "emoji": "🦛", + "name": "hippopotamus" + }, + { + "emoji": "🐭", + "name": "mouse face" + }, + { + "emoji": "🐁", + "name": "mouse" + }, + { + "emoji": "🐀", + "name": "rat" + }, + { + "emoji": "🐹", + "name": "hamster" + }, + { + "emoji": "🐰", + "name": "rabbit face" + }, + { + "emoji": "🐇", + "name": "rabbit" + }, + { + "emoji": "🐿️", + "name": "chipmunk" + }, + { + "emoji": "🦫", + "name": "beaver" + }, + { + "emoji": "🦔", + "name": "hedgehog" + }, + { + "emoji": "🦇", + "name": "bat" + }, + { + "emoji": "🐻", + "name": "bear" + }, + { + "emoji": "🐻‍❄️", + "name": "polar bear" + }, + { + "emoji": "🐨", + "name": "koala" + }, + { + "emoji": "🐼", + "name": "panda" + }, + { + "emoji": "🦥", + "name": "sloth" + }, + { + "emoji": "🦦", + "name": "otter" + }, + { + "emoji": "🦨", + "name": "skunk" + }, + { + "emoji": "🦘", + "name": "kangaroo" + }, + { + "emoji": "🦡", + "name": "badger" + }, + { + "emoji": "🐾", + "name": "paw prints" + }, + { + "emoji": "🦃", + "name": "turkey" + }, + { + "emoji": "🐔", + "name": "chicken" + }, + { + "emoji": "🐓", + "name": "rooster" + }, + { + "emoji": "🐣", + "name": "hatching chick" + }, + { + "emoji": "🐤", + "name": "baby chick" + }, + { + "emoji": "🐥", + "name": "front-facing baby chick" + }, + { + "emoji": "🐦", + "name": "bird" + }, + { + "emoji": "🐧", + "name": "penguin" + }, + { + "emoji": "🕊️", + "name": "dove" + }, + { + "emoji": "🦅", + "name": "eagle" + }, + { + "emoji": "🦆", + "name": "duck" + }, + { + "emoji": "🦢", + "name": "swan" + }, + { + "emoji": "🦉", + "name": "owl" + }, + { + "emoji": "🦤", + "name": "dodo" + }, + { + "emoji": "🪶", + "name": "feather" + }, + { + "emoji": "🦩", + "name": "flamingo" + }, + { + "emoji": "🦚", + "name": "peacock" + }, + { + "emoji": "🦜", + "name": "parrot" + }, + { + "emoji": "🪽", + "name": "wing" + }, + { + "emoji": "🐦‍⬛", + "name": "black bird" + }, + { + "emoji": "🪿", + "name": "goose" + }, + { + "emoji": "🐦‍🔥", + "name": "phoenix" + }, + { + "emoji": "🐸", + "name": "frog" + }, + { + "emoji": "🐊", + "name": "crocodile" + }, + { + "emoji": "🐢", + "name": "turtle" + }, + { + "emoji": "🦎", + "name": "lizard" + }, + { + "emoji": "🐍", + "name": "snake" + }, + { + "emoji": "🐲", + "name": "dragon face" + }, + { + "emoji": "🐉", + "name": "dragon" + }, + { + "emoji": "🦕", + "name": "sauropod" + }, + { + "emoji": "🦖", + "name": "T-Rex" + }, + { + "emoji": "🐳", + "name": "spouting whale" + }, + { + "emoji": "🐋", + "name": "whale" + }, + { + "emoji": "🐬", + "name": "dolphin" + }, + { + "emoji": "🦭", + "name": "seal" + }, + { + "emoji": "🐟", + "name": "fish" + }, + { + "emoji": "🐠", + "name": "tropical fish" + }, + { + "emoji": "🐡", + "name": "blowfish" + }, + { + "emoji": "🦈", + "name": "shark" + }, + { + "emoji": "🐙", + "name": "octopus" + }, + { + "emoji": "🐚", + "name": "spiral shell" + }, + { + "emoji": "🪸", + "name": "coral" + }, + { + "emoji": "🪼", + "name": "jellyfish" + }, + { + "emoji": "🐌", + "name": "snail" + }, + { + "emoji": "🦋", + "name": "butterfly" + }, + { + "emoji": "🐛", + "name": "bug" + }, + { + "emoji": "🐜", + "name": "ant" + }, + { + "emoji": "🐝", + "name": "honeybee" + }, + { + "emoji": "🪲", + "name": "beetle" + }, + { + "emoji": "🐞", + "name": "lady beetle" + }, + { + "emoji": "🦗", + "name": "cricket" + }, + { + "emoji": "🪳", + "name": "cockroach" + }, + { + "emoji": "🕷️", + "name": "spider" + }, + { + "emoji": "🕸️", + "name": "spider web" + }, + { + "emoji": "🦂", + "name": "scorpion" + }, + { + "emoji": "🦟", + "name": "mosquito" + }, + { + "emoji": "🪰", + "name": "fly" + }, + { + "emoji": "🪱", + "name": "worm" + }, + { + "emoji": "🦠", + "name": "microbe" + }, + { + "emoji": "💐", + "name": "bouquet" + }, + { + "emoji": "🌸", + "name": "cherry blossom" + }, + { + "emoji": "💮", + "name": "white flower" + }, + { + "emoji": "🪷", + "name": "lotus" + }, + { + "emoji": "🏵️", + "name": "rosette" + }, + { + "emoji": "🌹", + "name": "rose" + }, + { + "emoji": "🥀", + "name": "wilted flower" + }, + { + "emoji": "🌺", + "name": "hibiscus" + }, + { + "emoji": "🌻", + "name": "sunflower" + }, + { + "emoji": "🌼", + "name": "blossom" + }, + { + "emoji": "🌷", + "name": "tulip" + }, + { + "emoji": "🪻", + "name": "hyacinth" + }, + { + "emoji": "🌱", + "name": "seedling" + }, + { + "emoji": "🪴", + "name": "potted plant" + }, + { + "emoji": "🌲", + "name": "evergreen tree" + }, + { + "emoji": "🌳", + "name": "deciduous tree" + }, + { + "emoji": "🌴", + "name": "palm tree" + }, + { + "emoji": "🌵", + "name": "cactus" + }, + { + "emoji": "🌾", + "name": "sheaf of rice" + }, + { + "emoji": "🌿", + "name": "herb" + }, + { + "emoji": "☘️", + "name": "shamrock" + }, + { + "emoji": "🍀", + "name": "four leaf clover" + }, + { + "emoji": "🍁", + "name": "maple leaf" + }, + { + "emoji": "🍂", + "name": "fallen leaf" + }, + { + "emoji": "🍃", + "name": "leaf fluttering in wind" + }, + { + "emoji": "🪹", + "name": "empty nest" + }, + { + "emoji": "🪺", + "name": "nest with eggs" + }, + { + "emoji": "🍄", + "name": "mushroom" + } + ], + "Food & Drink": [ + { + "emoji": "🍇", + "name": "grapes" + }, + { + "emoji": "🍈", + "name": "melon" + }, + { + "emoji": "🍉", + "name": "watermelon" + }, + { + "emoji": "🍊", + "name": "tangerine" + }, + { + "emoji": "🍋", + "name": "lemon" + }, + { + "emoji": "🍋‍🟩", + "name": "lime" + }, + { + "emoji": "🍌", + "name": "banana" + }, + { + "emoji": "🍍", + "name": "pineapple" + }, + { + "emoji": "🥭", + "name": "mango" + }, + { + "emoji": "🍎", + "name": "red apple" + }, + { + "emoji": "🍏", + "name": "green apple" + }, + { + "emoji": "🍐", + "name": "pear" + }, + { + "emoji": "🍑", + "name": "peach" + }, + { + "emoji": "🍒", + "name": "cherries" + }, + { + "emoji": "🍓", + "name": "strawberry" + }, + { + "emoji": "🫐", + "name": "blueberries" + }, + { + "emoji": "🥝", + "name": "kiwi fruit" + }, + { + "emoji": "🍅", + "name": "tomato" + }, + { + "emoji": "🫒", + "name": "olive" + }, + { + "emoji": "🥥", + "name": "coconut" + }, + { + "emoji": "🥑", + "name": "avocado" + }, + { + "emoji": "🍆", + "name": "eggplant" + }, + { + "emoji": "🥔", + "name": "potato" + }, + { + "emoji": "🥕", + "name": "carrot" + }, + { + "emoji": "🌽", + "name": "ear of corn" + }, + { + "emoji": "🌶️", + "name": "hot pepper" + }, + { + "emoji": "🫑", + "name": "bell pepper" + }, + { + "emoji": "🥒", + "name": "cucumber" + }, + { + "emoji": "🥬", + "name": "leafy green" + }, + { + "emoji": "🥦", + "name": "broccoli" + }, + { + "emoji": "🧄", + "name": "garlic" + }, + { + "emoji": "🧅", + "name": "onion" + }, + { + "emoji": "🥜", + "name": "peanuts" + }, + { + "emoji": "🫘", + "name": "beans" + }, + { + "emoji": "🌰", + "name": "chestnut" + }, + { + "emoji": "🫚", + "name": "ginger root" + }, + { + "emoji": "🫛", + "name": "pea pod" + }, + { + "emoji": "🍄‍🟫", + "name": "brown mushroom" + }, + { + "emoji": "🍞", + "name": "bread" + }, + { + "emoji": "🥐", + "name": "croissant" + }, + { + "emoji": "🥖", + "name": "baguette bread" + }, + { + "emoji": "🫓", + "name": "flatbread" + }, + { + "emoji": "🥨", + "name": "pretzel" + }, + { + "emoji": "🥯", + "name": "bagel" + }, + { + "emoji": "🥞", + "name": "pancakes" + }, + { + "emoji": "🧇", + "name": "waffle" + }, + { + "emoji": "🧀", + "name": "cheese wedge" + }, + { + "emoji": "🍖", + "name": "meat on bone" + }, + { + "emoji": "🍗", + "name": "poultry leg" + }, + { + "emoji": "🥩", + "name": "cut of meat" + }, + { + "emoji": "🥓", + "name": "bacon" + }, + { + "emoji": "🍔", + "name": "hamburger" + }, + { + "emoji": "🍟", + "name": "french fries" + }, + { + "emoji": "🍕", + "name": "pizza" + }, + { + "emoji": "🌭", + "name": "hot dog" + }, + { + "emoji": "🥪", + "name": "sandwich" + }, + { + "emoji": "🌮", + "name": "taco" + }, + { + "emoji": "🌯", + "name": "burrito" + }, + { + "emoji": "🫔", + "name": "tamale" + }, + { + "emoji": "🥙", + "name": "stuffed flatbread" + }, + { + "emoji": "🧆", + "name": "falafel" + }, + { + "emoji": "🥚", + "name": "egg" + }, + { + "emoji": "🍳", + "name": "cooking" + }, + { + "emoji": "🥘", + "name": "shallow pan of food" + }, + { + "emoji": "🍲", + "name": "pot of food" + }, + { + "emoji": "🫕", + "name": "fondue" + }, + { + "emoji": "🥣", + "name": "bowl with spoon" + }, + { + "emoji": "🥗", + "name": "green salad" + }, + { + "emoji": "🍿", + "name": "popcorn" + }, + { + "emoji": "🧈", + "name": "butter" + }, + { + "emoji": "🧂", + "name": "salt" + }, + { + "emoji": "🥫", + "name": "canned food" + }, + { + "emoji": "🍱", + "name": "bento box" + }, + { + "emoji": "🍘", + "name": "rice cracker" + }, + { + "emoji": "🍙", + "name": "rice ball" + }, + { + "emoji": "🍚", + "name": "cooked rice" + }, + { + "emoji": "🍛", + "name": "curry rice" + }, + { + "emoji": "🍜", + "name": "steaming bowl" + }, + { + "emoji": "🍝", + "name": "spaghetti" + }, + { + "emoji": "🍠", + "name": "roasted sweet potato" + }, + { + "emoji": "🍢", + "name": "oden" + }, + { + "emoji": "🍣", + "name": "sushi" + }, + { + "emoji": "🍤", + "name": "fried shrimp" + }, + { + "emoji": "🍥", + "name": "fish cake with swirl" + }, + { + "emoji": "🥮", + "name": "moon cake" + }, + { + "emoji": "🍡", + "name": "dango" + }, + { + "emoji": "🥟", + "name": "dumpling" + }, + { + "emoji": "🥠", + "name": "fortune cookie" + }, + { + "emoji": "🥡", + "name": "takeout box" + }, + { + "emoji": "🦀", + "name": "crab" + }, + { + "emoji": "🦞", + "name": "lobster" + }, + { + "emoji": "🦐", + "name": "shrimp" + }, + { + "emoji": "🦑", + "name": "squid" + }, + { + "emoji": "🦪", + "name": "oyster" + }, + { + "emoji": "🍦", + "name": "soft ice cream" + }, + { + "emoji": "🍧", + "name": "shaved ice" + }, + { + "emoji": "🍨", + "name": "ice cream" + }, + { + "emoji": "🍩", + "name": "doughnut" + }, + { + "emoji": "🍪", + "name": "cookie" + }, + { + "emoji": "🎂", + "name": "birthday cake" + }, + { + "emoji": "🍰", + "name": "shortcake" + }, + { + "emoji": "🧁", + "name": "cupcake" + }, + { + "emoji": "🥧", + "name": "pie" + }, + { + "emoji": "🍫", + "name": "chocolate bar" + }, + { + "emoji": "🍬", + "name": "candy" + }, + { + "emoji": "🍭", + "name": "lollipop" + }, + { + "emoji": "🍮", + "name": "custard" + }, + { + "emoji": "🍯", + "name": "honey pot" + }, + { + "emoji": "🍼", + "name": "baby bottle" + }, + { + "emoji": "🥛", + "name": "glass of milk" + }, + { + "emoji": "☕", + "name": "hot beverage" + }, + { + "emoji": "🫖", + "name": "teapot" + }, + { + "emoji": "🍵", + "name": "teacup without handle" + }, + { + "emoji": "🍶", + "name": "sake" + }, + { + "emoji": "🍾", + "name": "bottle with popping cork" + }, + { + "emoji": "🍷", + "name": "wine glass" + }, + { + "emoji": "🍸", + "name": "cocktail glass" + }, + { + "emoji": "🍹", + "name": "tropical drink" + }, + { + "emoji": "🍺", + "name": "beer mug" + }, + { + "emoji": "🍻", + "name": "clinking beer mugs" + }, + { + "emoji": "🥂", + "name": "clinking glasses" + }, + { + "emoji": "🥃", + "name": "tumbler glass" + }, + { + "emoji": "🫗", + "name": "pouring liquid" + }, + { + "emoji": "🥤", + "name": "cup with straw" + }, + { + "emoji": "🧋", + "name": "bubble tea" + }, + { + "emoji": "🧃", + "name": "beverage box" + }, + { + "emoji": "🧉", + "name": "mate" + }, + { + "emoji": "🧊", + "name": "ice" + }, + { + "emoji": "🥢", + "name": "chopsticks" + }, + { + "emoji": "🍽️", + "name": "fork and knife with plate" + }, + { + "emoji": "🍴", + "name": "fork and knife" + }, + { + "emoji": "🥄", + "name": "spoon" + }, + { + "emoji": "🔪", + "name": "kitchen knife" + }, + { + "emoji": "🫙", + "name": "jar" + }, + { + "emoji": "🏺", + "name": "amphora" + } + ], + "Travel & Places": [ + { + "emoji": "🌍", + "name": "globe showing Europe-Africa" + }, + { + "emoji": "🌎", + "name": "globe showing Americas" + }, + { + "emoji": "🌏", + "name": "globe showing Asia-Australia" + }, + { + "emoji": "🌐", + "name": "globe with meridians" + }, + { + "emoji": "🗺️", + "name": "world map" + }, + { + "emoji": "🗾", + "name": "map of Japan" + }, + { + "emoji": "🧭", + "name": "compass" + }, + { + "emoji": "🏔️", + "name": "snow-capped mountain" + }, + { + "emoji": "⛰️", + "name": "mountain" + }, + { + "emoji": "🌋", + "name": "volcano" + }, + { + "emoji": "🗻", + "name": "mount fuji" + }, + { + "emoji": "🏕️", + "name": "camping" + }, + { + "emoji": "🏖️", + "name": "beach with umbrella" + }, + { + "emoji": "🏜️", + "name": "desert" + }, + { + "emoji": "🏝️", + "name": "desert island" + }, + { + "emoji": "🏞️", + "name": "national park" + }, + { + "emoji": "🏟️", + "name": "stadium" + }, + { + "emoji": "🏛️", + "name": "classical building" + }, + { + "emoji": "🏗️", + "name": "building construction" + }, + { + "emoji": "🧱", + "name": "brick" + }, + { + "emoji": "🪨", + "name": "rock" + }, + { + "emoji": "🪵", + "name": "wood" + }, + { + "emoji": "🛖", + "name": "hut" + }, + { + "emoji": "🏘️", + "name": "houses" + }, + { + "emoji": "🏚️", + "name": "derelict house" + }, + { + "emoji": "🏠", + "name": "house" + }, + { + "emoji": "🏡", + "name": "house with garden" + }, + { + "emoji": "🏢", + "name": "office building" + }, + { + "emoji": "🏣", + "name": "Japanese post office" + }, + { + "emoji": "🏤", + "name": "post office" + }, + { + "emoji": "🏥", + "name": "hospital" + }, + { + "emoji": "🏦", + "name": "bank" + }, + { + "emoji": "🏨", + "name": "hotel" + }, + { + "emoji": "🏩", + "name": "love hotel" + }, + { + "emoji": "🏪", + "name": "convenience store" + }, + { + "emoji": "🏫", + "name": "school" + }, + { + "emoji": "🏬", + "name": "department store" + }, + { + "emoji": "🏭", + "name": "factory" + }, + { + "emoji": "🏯", + "name": "Japanese castle" + }, + { + "emoji": "🏰", + "name": "castle" + }, + { + "emoji": "💒", + "name": "wedding" + }, + { + "emoji": "🗼", + "name": "Tokyo tower" + }, + { + "emoji": "🗽", + "name": "Statue of Liberty" + }, + { + "emoji": "⛪", + "name": "church" + }, + { + "emoji": "🕌", + "name": "mosque" + }, + { + "emoji": "🛕", + "name": "hindu temple" + }, + { + "emoji": "🕍", + "name": "synagogue" + }, + { + "emoji": "⛩️", + "name": "shinto shrine" + }, + { + "emoji": "🕋", + "name": "kaaba" + }, + { + "emoji": "⛲", + "name": "fountain" + }, + { + "emoji": "⛺", + "name": "tent" + }, + { + "emoji": "🌁", + "name": "foggy" + }, + { + "emoji": "🌃", + "name": "night with stars" + }, + { + "emoji": "🏙️", + "name": "cityscape" + }, + { + "emoji": "🌄", + "name": "sunrise over mountains" + }, + { + "emoji": "🌅", + "name": "sunrise" + }, + { + "emoji": "🌆", + "name": "cityscape at dusk" + }, + { + "emoji": "🌇", + "name": "sunset" + }, + { + "emoji": "🌉", + "name": "bridge at night" + }, + { + "emoji": "♨️", + "name": "hot springs" + }, + { + "emoji": "🎠", + "name": "carousel horse" + }, + { + "emoji": "🛝", + "name": "playground slide" + }, + { + "emoji": "🎡", + "name": "ferris wheel" + }, + { + "emoji": "🎢", + "name": "roller coaster" + }, + { + "emoji": "💈", + "name": "barber pole" + }, + { + "emoji": "🎪", + "name": "circus tent" + }, + { + "emoji": "🚂", + "name": "locomotive" + }, + { + "emoji": "🚃", + "name": "railway car" + }, + { + "emoji": "🚄", + "name": "high-speed train" + }, + { + "emoji": "🚅", + "name": "bullet train" + }, + { + "emoji": "🚆", + "name": "train" + }, + { + "emoji": "🚇", + "name": "metro" + }, + { + "emoji": "🚈", + "name": "light rail" + }, + { + "emoji": "🚉", + "name": "station" + }, + { + "emoji": "🚊", + "name": "tram" + }, + { + "emoji": "🚝", + "name": "monorail" + }, + { + "emoji": "🚞", + "name": "mountain railway" + }, + { + "emoji": "🚋", + "name": "tram car" + }, + { + "emoji": "🚌", + "name": "bus" + }, + { + "emoji": "🚍", + "name": "oncoming bus" + }, + { + "emoji": "🚎", + "name": "trolleybus" + }, + { + "emoji": "🚐", + "name": "minibus" + }, + { + "emoji": "🚑", + "name": "ambulance" + }, + { + "emoji": "🚒", + "name": "fire engine" + }, + { + "emoji": "🚓", + "name": "police car" + }, + { + "emoji": "🚔", + "name": "oncoming police car" + }, + { + "emoji": "🚕", + "name": "taxi" + }, + { + "emoji": "🚖", + "name": "oncoming taxi" + }, + { + "emoji": "🚗", + "name": "automobile" + }, + { + "emoji": "🚘", + "name": "oncoming automobile" + }, + { + "emoji": "🚙", + "name": "sport utility vehicle" + }, + { + "emoji": "🛻", + "name": "pickup truck" + }, + { + "emoji": "🚚", + "name": "delivery truck" + }, + { + "emoji": "🚛", + "name": "articulated lorry" + }, + { + "emoji": "🚜", + "name": "tractor" + }, + { + "emoji": "🏎️", + "name": "racing car" + }, + { + "emoji": "🏍️", + "name": "motorcycle" + }, + { + "emoji": "🛵", + "name": "motor scooter" + }, + { + "emoji": "🦽", + "name": "manual wheelchair" + }, + { + "emoji": "🦼", + "name": "motorized wheelchair" + }, + { + "emoji": "🛺", + "name": "auto rickshaw" + }, + { + "emoji": "🚲", + "name": "bicycle" + }, + { + "emoji": "🛴", + "name": "kick scooter" + }, + { + "emoji": "🛹", + "name": "skateboard" + }, + { + "emoji": "🛼", + "name": "roller skate" + }, + { + "emoji": "🚏", + "name": "bus stop" + }, + { + "emoji": "🛣️", + "name": "motorway" + }, + { + "emoji": "🛤️", + "name": "railway track" + }, + { + "emoji": "🛢️", + "name": "oil drum" + }, + { + "emoji": "⛽", + "name": "fuel pump" + }, + { + "emoji": "🛞", + "name": "wheel" + }, + { + "emoji": "🚨", + "name": "police car light" + }, + { + "emoji": "🚥", + "name": "horizontal traffic light" + }, + { + "emoji": "🚦", + "name": "vertical traffic light" + }, + { + "emoji": "🛑", + "name": "stop sign" + }, + { + "emoji": "🚧", + "name": "construction" + }, + { + "emoji": "⚓", + "name": "anchor" + }, + { + "emoji": "🛟", + "name": "ring buoy" + }, + { + "emoji": "⛵", + "name": "sailboat" + }, + { + "emoji": "🛶", + "name": "canoe" + }, + { + "emoji": "🚤", + "name": "speedboat" + }, + { + "emoji": "🛳️", + "name": "passenger ship" + }, + { + "emoji": "⛴️", + "name": "ferry" + }, + { + "emoji": "🛥️", + "name": "motor boat" + }, + { + "emoji": "🚢", + "name": "ship" + }, + { + "emoji": "✈️", + "name": "airplane" + }, + { + "emoji": "🛩️", + "name": "small airplane" + }, + { + "emoji": "🛫", + "name": "airplane departure" + }, + { + "emoji": "🛬", + "name": "airplane arrival" + }, + { + "emoji": "🪂", + "name": "parachute" + }, + { + "emoji": "💺", + "name": "seat" + }, + { + "emoji": "🚁", + "name": "helicopter" + }, + { + "emoji": "🚟", + "name": "suspension railway" + }, + { + "emoji": "🚠", + "name": "mountain cableway" + }, + { + "emoji": "🚡", + "name": "aerial tramway" + }, + { + "emoji": "🛰️", + "name": "satellite" + }, + { + "emoji": "🚀", + "name": "rocket" + }, + { + "emoji": "🛸", + "name": "flying saucer" + }, + { + "emoji": "🛎️", + "name": "bellhop bell" + }, + { + "emoji": "🧳", + "name": "luggage" + }, + { + "emoji": "⌛", + "name": "hourglass done" + }, + { + "emoji": "⏳", + "name": "hourglass not done" + }, + { + "emoji": "⌚", + "name": "watch" + }, + { + "emoji": "⏰", + "name": "alarm clock" + }, + { + "emoji": "⏱️", + "name": "stopwatch" + }, + { + "emoji": "⏲️", + "name": "timer clock" + }, + { + "emoji": "🕰️", + "name": "mantelpiece clock" + }, + { + "emoji": "🕛", + "name": "twelve o’clock" + }, + { + "emoji": "🕧", + "name": "twelve-thirty" + }, + { + "emoji": "🕐", + "name": "one o’clock" + }, + { + "emoji": "🕜", + "name": "one-thirty" + }, + { + "emoji": "🕑", + "name": "two o’clock" + }, + { + "emoji": "🕝", + "name": "two-thirty" + }, + { + "emoji": "🕒", + "name": "three o’clock" + }, + { + "emoji": "🕞", + "name": "three-thirty" + }, + { + "emoji": "🕓", + "name": "four o’clock" + }, + { + "emoji": "🕟", + "name": "four-thirty" + }, + { + "emoji": "🕔", + "name": "five o’clock" + }, + { + "emoji": "🕠", + "name": "five-thirty" + }, + { + "emoji": "🕕", + "name": "six o’clock" + }, + { + "emoji": "🕡", + "name": "six-thirty" + }, + { + "emoji": "🕖", + "name": "seven o’clock" + }, + { + "emoji": "🕢", + "name": "seven-thirty" + }, + { + "emoji": "🕗", + "name": "eight o’clock" + }, + { + "emoji": "🕣", + "name": "eight-thirty" + }, + { + "emoji": "🕘", + "name": "nine o’clock" + }, + { + "emoji": "🕤", + "name": "nine-thirty" + }, + { + "emoji": "🕙", + "name": "ten o’clock" + }, + { + "emoji": "🕥", + "name": "ten-thirty" + }, + { + "emoji": "🕚", + "name": "eleven o’clock" + }, + { + "emoji": "🕦", + "name": "eleven-thirty" + }, + { + "emoji": "🌑", + "name": "new moon" + }, + { + "emoji": "🌒", + "name": "waxing crescent moon" + }, + { + "emoji": "🌓", + "name": "first quarter moon" + }, + { + "emoji": "🌔", + "name": "waxing gibbous moon" + }, + { + "emoji": "🌕", + "name": "full moon" + }, + { + "emoji": "🌖", + "name": "waning gibbous moon" + }, + { + "emoji": "🌗", + "name": "last quarter moon" + }, + { + "emoji": "🌘", + "name": "waning crescent moon" + }, + { + "emoji": "🌙", + "name": "crescent moon" + }, + { + "emoji": "🌚", + "name": "new moon face" + }, + { + "emoji": "🌛", + "name": "first quarter moon face" + }, + { + "emoji": "🌜", + "name": "last quarter moon face" + }, + { + "emoji": "🌡️", + "name": "thermometer" + }, + { + "emoji": "☀️", + "name": "sun" + }, + { + "emoji": "🌝", + "name": "full moon face" + }, + { + "emoji": "🌞", + "name": "sun with face" + }, + { + "emoji": "🪐", + "name": "ringed planet" + }, + { + "emoji": "⭐", + "name": "star" + }, + { + "emoji": "🌟", + "name": "glowing star" + }, + { + "emoji": "🌠", + "name": "shooting star" + }, + { + "emoji": "🌌", + "name": "milky way" + }, + { + "emoji": "☁️", + "name": "cloud" + }, + { + "emoji": "⛅", + "name": "sun behind cloud" + }, + { + "emoji": "⛈️", + "name": "cloud with lightning and rain" + }, + { + "emoji": "🌤️", + "name": "sun behind small cloud" + }, + { + "emoji": "🌥️", + "name": "sun behind large cloud" + }, + { + "emoji": "🌦️", + "name": "sun behind rain cloud" + }, + { + "emoji": "🌧️", + "name": "cloud with rain" + }, + { + "emoji": "🌨️", + "name": "cloud with snow" + }, + { + "emoji": "🌩️", + "name": "cloud with lightning" + }, + { + "emoji": "🌪️", + "name": "tornado" + }, + { + "emoji": "🌫️", + "name": "fog" + }, + { + "emoji": "🌬️", + "name": "wind face" + }, + { + "emoji": "🌀", + "name": "cyclone" + }, + { + "emoji": "🌈", + "name": "rainbow" + }, + { + "emoji": "🌂", + "name": "closed umbrella" + }, + { + "emoji": "☂️", + "name": "umbrella" + }, + { + "emoji": "☔", + "name": "umbrella with rain drops" + }, + { + "emoji": "⛱️", + "name": "umbrella on ground" + }, + { + "emoji": "⚡", + "name": "high voltage" + }, + { + "emoji": "❄️", + "name": "snowflake" + }, + { + "emoji": "☃️", + "name": "snowman" + }, + { + "emoji": "⛄", + "name": "snowman without snow" + }, + { + "emoji": "☄️", + "name": "comet" + }, + { + "emoji": "🔥", + "name": "fire" + }, + { + "emoji": "💧", + "name": "droplet" + }, + { + "emoji": "🌊", + "name": "water wave" + } + ], + "Activities": [ + { + "emoji": "🎃", + "name": "jack-o-lantern" + }, + { + "emoji": "🎄", + "name": "Christmas tree" + }, + { + "emoji": "🎆", + "name": "fireworks" + }, + { + "emoji": "🎇", + "name": "sparkler" + }, + { + "emoji": "🧨", + "name": "firecracker" + }, + { + "emoji": "✨", + "name": "sparkles" + }, + { + "emoji": "🎈", + "name": "balloon" + }, + { + "emoji": "🎉", + "name": "party popper" + }, + { + "emoji": "🎊", + "name": "confetti ball" + }, + { + "emoji": "🎋", + "name": "tanabata tree" + }, + { + "emoji": "🎍", + "name": "pine decoration" + }, + { + "emoji": "🎎", + "name": "Japanese dolls" + }, + { + "emoji": "🎏", + "name": "carp streamer" + }, + { + "emoji": "🎐", + "name": "wind chime" + }, + { + "emoji": "🎑", + "name": "moon viewing ceremony" + }, + { + "emoji": "🧧", + "name": "red envelope" + }, + { + "emoji": "🎀", + "name": "ribbon" + }, + { + "emoji": "🎁", + "name": "wrapped gift" + }, + { + "emoji": "🎗️", + "name": "reminder ribbon" + }, + { + "emoji": "🎟️", + "name": "admission tickets" + }, + { + "emoji": "🎫", + "name": "ticket" + }, + { + "emoji": "🎖️", + "name": "military medal" + }, + { + "emoji": "🏆", + "name": "trophy" + }, + { + "emoji": "🏅", + "name": "sports medal" + }, + { + "emoji": "🥇", + "name": "1st place medal" + }, + { + "emoji": "🥈", + "name": "2nd place medal" + }, + { + "emoji": "🥉", + "name": "3rd place medal" + }, + { + "emoji": "⚽", + "name": "soccer ball" + }, + { + "emoji": "⚾", + "name": "baseball" + }, + { + "emoji": "🥎", + "name": "softball" + }, + { + "emoji": "🏀", + "name": "basketball" + }, + { + "emoji": "🏐", + "name": "volleyball" + }, + { + "emoji": "🏈", + "name": "american football" + }, + { + "emoji": "🏉", + "name": "rugby football" + }, + { + "emoji": "🎾", + "name": "tennis" + }, + { + "emoji": "🥏", + "name": "flying disc" + }, + { + "emoji": "🎳", + "name": "bowling" + }, + { + "emoji": "🏏", + "name": "cricket game" + }, + { + "emoji": "🏑", + "name": "field hockey" + }, + { + "emoji": "🏒", + "name": "ice hockey" + }, + { + "emoji": "🥍", + "name": "lacrosse" + }, + { + "emoji": "🏓", + "name": "ping pong" + }, + { + "emoji": "🏸", + "name": "badminton" + }, + { + "emoji": "🥊", + "name": "boxing glove" + }, + { + "emoji": "🥋", + "name": "martial arts uniform" + }, + { + "emoji": "🥅", + "name": "goal net" + }, + { + "emoji": "⛳", + "name": "flag in hole" + }, + { + "emoji": "⛸️", + "name": "ice skate" + }, + { + "emoji": "🎣", + "name": "fishing pole" + }, + { + "emoji": "🤿", + "name": "diving mask" + }, + { + "emoji": "🎽", + "name": "running shirt" + }, + { + "emoji": "🎿", + "name": "skis" + }, + { + "emoji": "🛷", + "name": "sled" + }, + { + "emoji": "🥌", + "name": "curling stone" + }, + { + "emoji": "🎯", + "name": "bullseye" + }, + { + "emoji": "🪀", + "name": "yo-yo" + }, + { + "emoji": "🪁", + "name": "kite" + }, + { + "emoji": "🔫", + "name": "water pistol" + }, + { + "emoji": "🎱", + "name": "pool 8 ball" + }, + { + "emoji": "🔮", + "name": "crystal ball" + }, + { + "emoji": "🪄", + "name": "magic wand" + }, + { + "emoji": "🎮", + "name": "video game" + }, + { + "emoji": "🕹️", + "name": "joystick" + }, + { + "emoji": "🎰", + "name": "slot machine" + }, + { + "emoji": "🎲", + "name": "game die" + }, + { + "emoji": "🧩", + "name": "puzzle piece" + }, + { + "emoji": "🧸", + "name": "teddy bear" + }, + { + "emoji": "🪅", + "name": "piñata" + }, + { + "emoji": "🪩", + "name": "mirror ball" + }, + { + "emoji": "🪆", + "name": "nesting dolls" + }, + { + "emoji": "♠️", + "name": "spade suit" + }, + { + "emoji": "♥️", + "name": "heart suit" + }, + { + "emoji": "♦️", + "name": "diamond suit" + }, + { + "emoji": "♣️", + "name": "club suit" + }, + { + "emoji": "♟️", + "name": "chess pawn" + }, + { + "emoji": "🃏", + "name": "joker" + }, + { + "emoji": "🀄", + "name": "mahjong red dragon" + }, + { + "emoji": "🎴", + "name": "flower playing cards" + }, + { + "emoji": "🎭", + "name": "performing arts" + }, + { + "emoji": "🖼️", + "name": "framed picture" + }, + { + "emoji": "🎨", + "name": "artist palette" + }, + { + "emoji": "🧵", + "name": "thread" + }, + { + "emoji": "🪡", + "name": "sewing needle" + }, + { + "emoji": "🧶", + "name": "yarn" + }, + { + "emoji": "🪢", + "name": "knot" + } + ], + "Objects": [ + { + "emoji": "👓", + "name": "glasses" + }, + { + "emoji": "🕶️", + "name": "sunglasses" + }, + { + "emoji": "🥽", + "name": "goggles" + }, + { + "emoji": "🥼", + "name": "lab coat" + }, + { + "emoji": "🦺", + "name": "safety vest" + }, + { + "emoji": "👔", + "name": "necktie" + }, + { + "emoji": "👕", + "name": "t-shirt" + }, + { + "emoji": "👖", + "name": "jeans" + }, + { + "emoji": "🧣", + "name": "scarf" + }, + { + "emoji": "🧤", + "name": "gloves" + }, + { + "emoji": "🧥", + "name": "coat" + }, + { + "emoji": "🧦", + "name": "socks" + }, + { + "emoji": "👗", + "name": "dress" + }, + { + "emoji": "👘", + "name": "kimono" + }, + { + "emoji": "🥻", + "name": "sari" + }, + { + "emoji": "🩱", + "name": "one-piece swimsuit" + }, + { + "emoji": "🩲", + "name": "briefs" + }, + { + "emoji": "🩳", + "name": "shorts" + }, + { + "emoji": "👙", + "name": "bikini" + }, + { + "emoji": "👚", + "name": "woman’s clothes" + }, + { + "emoji": "🪭", + "name": "folding hand fan" + }, + { + "emoji": "👛", + "name": "purse" + }, + { + "emoji": "👜", + "name": "handbag" + }, + { + "emoji": "👝", + "name": "clutch bag" + }, + { + "emoji": "🛍️", + "name": "shopping bags" + }, + { + "emoji": "🎒", + "name": "backpack" + }, + { + "emoji": "🩴", + "name": "thong sandal" + }, + { + "emoji": "👞", + "name": "man’s shoe" + }, + { + "emoji": "👟", + "name": "running shoe" + }, + { + "emoji": "🥾", + "name": "hiking boot" + }, + { + "emoji": "🥿", + "name": "flat shoe" + }, + { + "emoji": "👠", + "name": "high-heeled shoe" + }, + { + "emoji": "👡", + "name": "woman’s sandal" + }, + { + "emoji": "🩰", + "name": "ballet shoes" + }, + { + "emoji": "👢", + "name": "woman’s boot" + }, + { + "emoji": "🪮", + "name": "hair pick" + }, + { + "emoji": "👑", + "name": "crown" + }, + { + "emoji": "👒", + "name": "woman’s hat" + }, + { + "emoji": "🎩", + "name": "top hat" + }, + { + "emoji": "🎓", + "name": "graduation cap" + }, + { + "emoji": "🧢", + "name": "billed cap" + }, + { + "emoji": "🪖", + "name": "military helmet" + }, + { + "emoji": "⛑️", + "name": "rescue worker’s helmet" + }, + { + "emoji": "📿", + "name": "prayer beads" + }, + { + "emoji": "💄", + "name": "lipstick" + }, + { + "emoji": "💍", + "name": "ring" + }, + { + "emoji": "💎", + "name": "gem stone" + }, + { + "emoji": "🔇", + "name": "muted speaker" + }, + { + "emoji": "🔈", + "name": "speaker low volume" + }, + { + "emoji": "🔉", + "name": "speaker medium volume" + }, + { + "emoji": "🔊", + "name": "speaker high volume" + }, + { + "emoji": "📢", + "name": "loudspeaker" + }, + { + "emoji": "📣", + "name": "megaphone" + }, + { + "emoji": "📯", + "name": "postal horn" + }, + { + "emoji": "🔔", + "name": "bell" + }, + { + "emoji": "🔕", + "name": "bell with slash" + }, + { + "emoji": "🎼", + "name": "musical score" + }, + { + "emoji": "🎵", + "name": "musical note" + }, + { + "emoji": "🎶", + "name": "musical notes" + }, + { + "emoji": "🎙️", + "name": "studio microphone" + }, + { + "emoji": "🎚️", + "name": "level slider" + }, + { + "emoji": "🎛️", + "name": "control knobs" + }, + { + "emoji": "🎤", + "name": "microphone" + }, + { + "emoji": "🎧", + "name": "headphone" + }, + { + "emoji": "📻", + "name": "radio" + }, + { + "emoji": "🎷", + "name": "saxophone" + }, + { + "emoji": "🪗", + "name": "accordion" + }, + { + "emoji": "🎸", + "name": "guitar" + }, + { + "emoji": "🎹", + "name": "musical keyboard" + }, + { + "emoji": "🎺", + "name": "trumpet" + }, + { + "emoji": "🎻", + "name": "violin" + }, + { + "emoji": "🪕", + "name": "banjo" + }, + { + "emoji": "🥁", + "name": "drum" + }, + { + "emoji": "🪘", + "name": "long drum" + }, + { + "emoji": "🪇", + "name": "maracas" + }, + { + "emoji": "🪈", + "name": "flute" + }, + { + "emoji": "📱", + "name": "mobile phone" + }, + { + "emoji": "📲", + "name": "mobile phone with arrow" + }, + { + "emoji": "☎️", + "name": "telephone" + }, + { + "emoji": "📞", + "name": "telephone receiver" + }, + { + "emoji": "📟", + "name": "pager" + }, + { + "emoji": "📠", + "name": "fax machine" + }, + { + "emoji": "🔋", + "name": "battery" + }, + { + "emoji": "🪫", + "name": "low battery" + }, + { + "emoji": "🔌", + "name": "electric plug" + }, + { + "emoji": "💻", + "name": "laptop" + }, + { + "emoji": "🖥️", + "name": "desktop computer" + }, + { + "emoji": "🖨️", + "name": "printer" + }, + { + "emoji": "⌨️", + "name": "keyboard" + }, + { + "emoji": "🖱️", + "name": "computer mouse" + }, + { + "emoji": "🖲️", + "name": "trackball" + }, + { + "emoji": "💽", + "name": "computer disk" + }, + { + "emoji": "💾", + "name": "floppy disk" + }, + { + "emoji": "💿", + "name": "optical disk" + }, + { + "emoji": "📀", + "name": "dvd" + }, + { + "emoji": "🧮", + "name": "abacus" + }, + { + "emoji": "🎥", + "name": "movie camera" + }, + { + "emoji": "🎞️", + "name": "film frames" + }, + { + "emoji": "📽️", + "name": "film projector" + }, + { + "emoji": "🎬", + "name": "clapper board" + }, + { + "emoji": "📺", + "name": "television" + }, + { + "emoji": "📷", + "name": "camera" + }, + { + "emoji": "📸", + "name": "camera with flash" + }, + { + "emoji": "📹", + "name": "video camera" + }, + { + "emoji": "📼", + "name": "videocassette" + }, + { + "emoji": "🔍", + "name": "magnifying glass tilted left" + }, + { + "emoji": "🔎", + "name": "magnifying glass tilted right" + }, + { + "emoji": "🕯️", + "name": "candle" + }, + { + "emoji": "💡", + "name": "light bulb" + }, + { + "emoji": "🔦", + "name": "flashlight" + }, + { + "emoji": "🏮", + "name": "red paper lantern" + }, + { + "emoji": "🪔", + "name": "diya lamp" + }, + { + "emoji": "📔", + "name": "notebook with decorative cover" + }, + { + "emoji": "📕", + "name": "closed book" + }, + { + "emoji": "📖", + "name": "open book" + }, + { + "emoji": "📗", + "name": "green book" + }, + { + "emoji": "📘", + "name": "blue book" + }, + { + "emoji": "📙", + "name": "orange book" + }, + { + "emoji": "📚", + "name": "books" + }, + { + "emoji": "📓", + "name": "notebook" + }, + { + "emoji": "📒", + "name": "ledger" + }, + { + "emoji": "📃", + "name": "page with curl" + }, + { + "emoji": "📜", + "name": "scroll" + }, + { + "emoji": "📄", + "name": "page facing up" + }, + { + "emoji": "📰", + "name": "newspaper" + }, + { + "emoji": "🗞️", + "name": "rolled-up newspaper" + }, + { + "emoji": "📑", + "name": "bookmark tabs" + }, + { + "emoji": "🔖", + "name": "bookmark" + }, + { + "emoji": "🏷️", + "name": "label" + }, + { + "emoji": "💰", + "name": "money bag" + }, + { + "emoji": "🪙", + "name": "coin" + }, + { + "emoji": "💴", + "name": "yen banknote" + }, + { + "emoji": "💵", + "name": "dollar banknote" + }, + { + "emoji": "💶", + "name": "euro banknote" + }, + { + "emoji": "💷", + "name": "pound banknote" + }, + { + "emoji": "💸", + "name": "money with wings" + }, + { + "emoji": "💳", + "name": "credit card" + }, + { + "emoji": "🧾", + "name": "receipt" + }, + { + "emoji": "💹", + "name": "chart increasing with yen" + }, + { + "emoji": "✉️", + "name": "envelope" + }, + { + "emoji": "📧", + "name": "e-mail" + }, + { + "emoji": "📨", + "name": "incoming envelope" + }, + { + "emoji": "📩", + "name": "envelope with arrow" + }, + { + "emoji": "📤", + "name": "outbox tray" + }, + { + "emoji": "📥", + "name": "inbox tray" + }, + { + "emoji": "📦", + "name": "package" + }, + { + "emoji": "📫", + "name": "closed mailbox with raised flag" + }, + { + "emoji": "📪", + "name": "closed mailbox with lowered flag" + }, + { + "emoji": "📬", + "name": "open mailbox with raised flag" + }, + { + "emoji": "📭", + "name": "open mailbox with lowered flag" + }, + { + "emoji": "📮", + "name": "postbox" + }, + { + "emoji": "🗳️", + "name": "ballot box with ballot" + }, + { + "emoji": "✏️", + "name": "pencil" + }, + { + "emoji": "✒️", + "name": "black nib" + }, + { + "emoji": "🖋️", + "name": "fountain pen" + }, + { + "emoji": "🖊️", + "name": "pen" + }, + { + "emoji": "🖌️", + "name": "paintbrush" + }, + { + "emoji": "🖍️", + "name": "crayon" + }, + { + "emoji": "📝", + "name": "memo" + }, + { + "emoji": "💼", + "name": "briefcase" + }, + { + "emoji": "📁", + "name": "file folder" + }, + { + "emoji": "📂", + "name": "open file folder" + }, + { + "emoji": "🗂️", + "name": "card index dividers" + }, + { + "emoji": "📅", + "name": "calendar" + }, + { + "emoji": "📆", + "name": "tear-off calendar" + }, + { + "emoji": "🗒️", + "name": "spiral notepad" + }, + { + "emoji": "🗓️", + "name": "spiral calendar" + }, + { + "emoji": "📇", + "name": "card index" + }, + { + "emoji": "📈", + "name": "chart increasing" + }, + { + "emoji": "📉", + "name": "chart decreasing" + }, + { + "emoji": "📊", + "name": "bar chart" + }, + { + "emoji": "📋", + "name": "clipboard" + }, + { + "emoji": "📌", + "name": "pushpin" + }, + { + "emoji": "📍", + "name": "round pushpin" + }, + { + "emoji": "📎", + "name": "paperclip" + }, + { + "emoji": "🖇️", + "name": "linked paperclips" + }, + { + "emoji": "📏", + "name": "straight ruler" + }, + { + "emoji": "📐", + "name": "triangular ruler" + }, + { + "emoji": "✂️", + "name": "scissors" + }, + { + "emoji": "🗃️", + "name": "card file box" + }, + { + "emoji": "🗄️", + "name": "file cabinet" + }, + { + "emoji": "🗑️", + "name": "wastebasket" + }, + { + "emoji": "🔒", + "name": "locked" + }, + { + "emoji": "🔓", + "name": "unlocked" + }, + { + "emoji": "🔏", + "name": "locked with pen" + }, + { + "emoji": "🔐", + "name": "locked with key" + }, + { + "emoji": "🔑", + "name": "key" + }, + { + "emoji": "🗝️", + "name": "old key" + }, + { + "emoji": "🔨", + "name": "hammer" + }, + { + "emoji": "🪓", + "name": "axe" + }, + { + "emoji": "⛏️", + "name": "pick" + }, + { + "emoji": "⚒️", + "name": "hammer and pick" + }, + { + "emoji": "🛠️", + "name": "hammer and wrench" + }, + { + "emoji": "🗡️", + "name": "dagger" + }, + { + "emoji": "⚔️", + "name": "crossed swords" + }, + { + "emoji": "💣", + "name": "bomb" + }, + { + "emoji": "🪃", + "name": "boomerang" + }, + { + "emoji": "🏹", + "name": "bow and arrow" + }, + { + "emoji": "🛡️", + "name": "shield" + }, + { + "emoji": "🪚", + "name": "carpentry saw" + }, + { + "emoji": "🔧", + "name": "wrench" + }, + { + "emoji": "🪛", + "name": "screwdriver" + }, + { + "emoji": "🔩", + "name": "nut and bolt" + }, + { + "emoji": "⚙️", + "name": "gear" + }, + { + "emoji": "🗜️", + "name": "clamp" + }, + { + "emoji": "⚖️", + "name": "balance scale" + }, + { + "emoji": "🦯", + "name": "white cane" + }, + { + "emoji": "🔗", + "name": "link" + }, + { + "emoji": "⛓️‍💥", + "name": "broken chain" + }, + { + "emoji": "⛓️", + "name": "chains" + }, + { + "emoji": "🪝", + "name": "hook" + }, + { + "emoji": "🧰", + "name": "toolbox" + }, + { + "emoji": "🧲", + "name": "magnet" + }, + { + "emoji": "🪜", + "name": "ladder" + }, + { + "emoji": "⚗️", + "name": "alembic" + }, + { + "emoji": "🧪", + "name": "test tube" + }, + { + "emoji": "🧫", + "name": "petri dish" + }, + { + "emoji": "🧬", + "name": "dna" + }, + { + "emoji": "🔬", + "name": "microscope" + }, + { + "emoji": "🔭", + "name": "telescope" + }, + { + "emoji": "📡", + "name": "satellite antenna" + }, + { + "emoji": "💉", + "name": "syringe" + }, + { + "emoji": "🩸", + "name": "drop of blood" + }, + { + "emoji": "💊", + "name": "pill" + }, + { + "emoji": "🩹", + "name": "adhesive bandage" + }, + { + "emoji": "🩼", + "name": "crutch" + }, + { + "emoji": "🩺", + "name": "stethoscope" + }, + { + "emoji": "🩻", + "name": "x-ray" + }, + { + "emoji": "🚪", + "name": "door" + }, + { + "emoji": "🛗", + "name": "elevator" + }, + { + "emoji": "🪞", + "name": "mirror" + }, + { + "emoji": "🪟", + "name": "window" + }, + { + "emoji": "🛏️", + "name": "bed" + }, + { + "emoji": "🛋️", + "name": "couch and lamp" + }, + { + "emoji": "🪑", + "name": "chair" + }, + { + "emoji": "🚽", + "name": "toilet" + }, + { + "emoji": "🪠", + "name": "plunger" + }, + { + "emoji": "🚿", + "name": "shower" + }, + { + "emoji": "🛁", + "name": "bathtub" + }, + { + "emoji": "🪤", + "name": "mouse trap" + }, + { + "emoji": "🪒", + "name": "razor" + }, + { + "emoji": "🧴", + "name": "lotion bottle" + }, + { + "emoji": "🧷", + "name": "safety pin" + }, + { + "emoji": "🧹", + "name": "broom" + }, + { + "emoji": "🧺", + "name": "basket" + }, + { + "emoji": "🧻", + "name": "roll of paper" + }, + { + "emoji": "🪣", + "name": "bucket" + }, + { + "emoji": "🧼", + "name": "soap" + }, + { + "emoji": "🫧", + "name": "bubbles" + }, + { + "emoji": "🪥", + "name": "toothbrush" + }, + { + "emoji": "🧽", + "name": "sponge" + }, + { + "emoji": "🧯", + "name": "fire extinguisher" + }, + { + "emoji": "🛒", + "name": "shopping cart" + }, + { + "emoji": "🚬", + "name": "cigarette" + }, + { + "emoji": "⚰️", + "name": "coffin" + }, + { + "emoji": "🪦", + "name": "headstone" + }, + { + "emoji": "⚱️", + "name": "funeral urn" + }, + { + "emoji": "🧿", + "name": "nazar amulet" + }, + { + "emoji": "🪬", + "name": "hamsa" + }, + { + "emoji": "🗿", + "name": "moai" + }, + { + "emoji": "🪧", + "name": "placard" + }, + { + "emoji": "🪪", + "name": "identification card" + } + ], + "Symbols": [ + { + "emoji": "🏧", + "name": "ATM sign" + }, + { + "emoji": "🚮", + "name": "litter in bin sign" + }, + { + "emoji": "🚰", + "name": "potable water" + }, + { + "emoji": "♿", + "name": "wheelchair symbol" + }, + { + "emoji": "🚹", + "name": "men’s room" + }, + { + "emoji": "🚺", + "name": "women’s room" + }, + { + "emoji": "🚻", + "name": "restroom" + }, + { + "emoji": "🚼", + "name": "baby symbol" + }, + { + "emoji": "🚾", + "name": "water closet" + }, + { + "emoji": "🛂", + "name": "passport control" + }, + { + "emoji": "🛃", + "name": "customs" + }, + { + "emoji": "🛄", + "name": "baggage claim" + }, + { + "emoji": "🛅", + "name": "left luggage" + }, + { + "emoji": "⚠️", + "name": "warning" + }, + { + "emoji": "🚸", + "name": "children crossing" + }, + { + "emoji": "⛔", + "name": "no entry" + }, + { + "emoji": "🚫", + "name": "prohibited" + }, + { + "emoji": "🚳", + "name": "no bicycles" + }, + { + "emoji": "🚭", + "name": "no smoking" + }, + { + "emoji": "🚯", + "name": "no littering" + }, + { + "emoji": "🚱", + "name": "non-potable water" + }, + { + "emoji": "🚷", + "name": "no pedestrians" + }, + { + "emoji": "📵", + "name": "no mobile phones" + }, + { + "emoji": "🔞", + "name": "no one under eighteen" + }, + { + "emoji": "☢️", + "name": "radioactive" + }, + { + "emoji": "☣️", + "name": "biohazard" + }, + { + "emoji": "⬆️", + "name": "up arrow" + }, + { + "emoji": "↗️", + "name": "up-right arrow" + }, + { + "emoji": "➡️", + "name": "right arrow" + }, + { + "emoji": "↘️", + "name": "down-right arrow" + }, + { + "emoji": "⬇️", + "name": "down arrow" + }, + { + "emoji": "↙️", + "name": "down-left arrow" + }, + { + "emoji": "⬅️", + "name": "left arrow" + }, + { + "emoji": "↖️", + "name": "up-left arrow" + }, + { + "emoji": "↕️", + "name": "up-down arrow" + }, + { + "emoji": "↔️", + "name": "left-right arrow" + }, + { + "emoji": "↩️", + "name": "right arrow curving left" + }, + { + "emoji": "↪️", + "name": "left arrow curving right" + }, + { + "emoji": "⤴️", + "name": "right arrow curving up" + }, + { + "emoji": "⤵️", + "name": "right arrow curving down" + }, + { + "emoji": "🔃", + "name": "clockwise vertical arrows" + }, + { + "emoji": "🔄", + "name": "counterclockwise arrows button" + }, + { + "emoji": "🔙", + "name": "BACK arrow" + }, + { + "emoji": "🔚", + "name": "END arrow" + }, + { + "emoji": "🔛", + "name": "ON! arrow" + }, + { + "emoji": "🔜", + "name": "SOON arrow" + }, + { + "emoji": "🔝", + "name": "TOP arrow" + }, + { + "emoji": "🛐", + "name": "place of worship" + }, + { + "emoji": "⚛️", + "name": "atom symbol" + }, + { + "emoji": "🕉️", + "name": "om" + }, + { + "emoji": "✡️", + "name": "star of David" + }, + { + "emoji": "☸️", + "name": "wheel of dharma" + }, + { + "emoji": "☯️", + "name": "yin yang" + }, + { + "emoji": "✝️", + "name": "latin cross" + }, + { + "emoji": "☦️", + "name": "orthodox cross" + }, + { + "emoji": "☪️", + "name": "star and crescent" + }, + { + "emoji": "☮️", + "name": "peace symbol" + }, + { + "emoji": "🕎", + "name": "menorah" + }, + { + "emoji": "🔯", + "name": "dotted six-pointed star" + }, + { + "emoji": "🪯", + "name": "khanda" + }, + { + "emoji": "♈", + "name": "Aries" + }, + { + "emoji": "♉", + "name": "Taurus" + }, + { + "emoji": "♊", + "name": "Gemini" + }, + { + "emoji": "♋", + "name": "Cancer" + }, + { + "emoji": "♌", + "name": "Leo" + }, + { + "emoji": "♍", + "name": "Virgo" + }, + { + "emoji": "♎", + "name": "Libra" + }, + { + "emoji": "♏", + "name": "Scorpio" + }, + { + "emoji": "♐", + "name": "Sagittarius" + }, + { + "emoji": "♑", + "name": "Capricorn" + }, + { + "emoji": "♒", + "name": "Aquarius" + }, + { + "emoji": "♓", + "name": "Pisces" + }, + { + "emoji": "⛎", + "name": "Ophiuchus" + }, + { + "emoji": "🔀", + "name": "shuffle tracks button" + }, + { + "emoji": "🔁", + "name": "repeat button" + }, + { + "emoji": "🔂", + "name": "repeat single button" + }, + { + "emoji": "▶️", + "name": "play button" + }, + { + "emoji": "⏩", + "name": "fast-forward button" + }, + { + "emoji": "⏭️", + "name": "next track button" + }, + { + "emoji": "⏯️", + "name": "play or pause button" + }, + { + "emoji": "◀️", + "name": "reverse button" + }, + { + "emoji": "⏪", + "name": "fast reverse button" + }, + { + "emoji": "⏮️", + "name": "last track button" + }, + { + "emoji": "🔼", + "name": "upwards button" + }, + { + "emoji": "⏫", + "name": "fast up button" + }, + { + "emoji": "🔽", + "name": "downwards button" + }, + { + "emoji": "⏬", + "name": "fast down button" + }, + { + "emoji": "⏸️", + "name": "pause button" + }, + { + "emoji": "⏹️", + "name": "stop button" + }, + { + "emoji": "⏺️", + "name": "record button" + }, + { + "emoji": "⏏️", + "name": "eject button" + }, + { + "emoji": "🎦", + "name": "cinema" + }, + { + "emoji": "🔅", + "name": "dim button" + }, + { + "emoji": "🔆", + "name": "bright button" + }, + { + "emoji": "📶", + "name": "antenna bars" + }, + { + "emoji": "🛜", + "name": "wireless" + }, + { + "emoji": "📳", + "name": "vibration mode" + }, + { + "emoji": "📴", + "name": "mobile phone off" + }, + { + "emoji": "♀️", + "name": "female sign" + }, + { + "emoji": "♂️", + "name": "male sign" + }, + { + "emoji": "⚧️", + "name": "transgender symbol" + }, + { + "emoji": "✖️", + "name": "multiply" + }, + { + "emoji": "➕", + "name": "plus" + }, + { + "emoji": "➖", + "name": "minus" + }, + { + "emoji": "➗", + "name": "divide" + }, + { + "emoji": "🟰", + "name": "heavy equals sign" + }, + { + "emoji": "♾️", + "name": "infinity" + }, + { + "emoji": "‼️", + "name": "double exclamation mark" + }, + { + "emoji": "⁉️", + "name": "exclamation question mark" + }, + { + "emoji": "❓", + "name": "red question mark" + }, + { + "emoji": "❔", + "name": "white question mark" + }, + { + "emoji": "❕", + "name": "white exclamation mark" + }, + { + "emoji": "❗", + "name": "red exclamation mark" + }, + { + "emoji": "〰️", + "name": "wavy dash" + }, + { + "emoji": "💱", + "name": "currency exchange" + }, + { + "emoji": "💲", + "name": "heavy dollar sign" + }, + { + "emoji": "⚕️", + "name": "medical symbol" + }, + { + "emoji": "♻️", + "name": "recycling symbol" + }, + { + "emoji": "⚜️", + "name": "fleur-de-lis" + }, + { + "emoji": "🔱", + "name": "trident emblem" + }, + { + "emoji": "📛", + "name": "name badge" + }, + { + "emoji": "🔰", + "name": "Japanese symbol for beginner" + }, + { + "emoji": "⭕", + "name": "hollow red circle" + }, + { + "emoji": "✅", + "name": "check mark button" + }, + { + "emoji": "☑️", + "name": "check box with check" + }, + { + "emoji": "✔️", + "name": "check mark" + }, + { + "emoji": "❌", + "name": "cross mark" + }, + { + "emoji": "❎", + "name": "cross mark button" + }, + { + "emoji": "➰", + "name": "curly loop" + }, + { + "emoji": "➿", + "name": "double curly loop" + }, + { + "emoji": "〽️", + "name": "part alternation mark" + }, + { + "emoji": "✳️", + "name": "eight-spoked asterisk" + }, + { + "emoji": "✴️", + "name": "eight-pointed star" + }, + { + "emoji": "❇️", + "name": "sparkle" + }, + { + "emoji": "©️", + "name": "copyright" + }, + { + "emoji": "®️", + "name": "registered" + }, + { + "emoji": "™️", + "name": "trade mark" + }, + { + "emoji": "#️⃣", + "name": "keycap #" + }, + { + "emoji": "*️⃣", + "name": "keycap *" + }, + { + "emoji": "0️⃣", + "name": "keycap 0" + }, + { + "emoji": "1️⃣", + "name": "keycap 1" + }, + { + "emoji": "2️⃣", + "name": "keycap 2" + }, + { + "emoji": "3️⃣", + "name": "keycap 3" + }, + { + "emoji": "4️⃣", + "name": "keycap 4" + }, + { + "emoji": "5️⃣", + "name": "keycap 5" + }, + { + "emoji": "6️⃣", + "name": "keycap 6" + }, + { + "emoji": "7️⃣", + "name": "keycap 7" + }, + { + "emoji": "8️⃣", + "name": "keycap 8" + }, + { + "emoji": "9️⃣", + "name": "keycap 9" + }, + { + "emoji": "🔟", + "name": "keycap 10" + }, + { + "emoji": "🔠", + "name": "input latin uppercase" + }, + { + "emoji": "🔡", + "name": "input latin lowercase" + }, + { + "emoji": "🔢", + "name": "input numbers" + }, + { + "emoji": "🔣", + "name": "input symbols" + }, + { + "emoji": "🔤", + "name": "input latin letters" + }, + { + "emoji": "🅰️", + "name": "A button (blood type)" + }, + { + "emoji": "🆎", + "name": "AB button (blood type)" + }, + { + "emoji": "🅱️", + "name": "B button (blood type)" + }, + { + "emoji": "🆑", + "name": "CL button" + }, + { + "emoji": "🆒", + "name": "COOL button" + }, + { + "emoji": "🆓", + "name": "FREE button" + }, + { + "emoji": "ℹ️", + "name": "information" + }, + { + "emoji": "🆔", + "name": "ID button" + }, + { + "emoji": "Ⓜ️", + "name": "circled M" + }, + { + "emoji": "🆕", + "name": "NEW button" + }, + { + "emoji": "🆖", + "name": "NG button" + }, + { + "emoji": "🅾️", + "name": "O button (blood type)" + }, + { + "emoji": "🆗", + "name": "OK button" + }, + { + "emoji": "🅿️", + "name": "P button" + }, + { + "emoji": "🆘", + "name": "SOS button" + }, + { + "emoji": "🆙", + "name": "UP! button" + }, + { + "emoji": "🆚", + "name": "VS button" + }, + { + "emoji": "🈁", + "name": "Japanese “here” button" + }, + { + "emoji": "🈂️", + "name": "Japanese “service charge” button" + }, + { + "emoji": "🈷️", + "name": "Japanese “monthly amount” button" + }, + { + "emoji": "🈶", + "name": "Japanese “not free of charge” button" + }, + { + "emoji": "🈯", + "name": "Japanese “reserved” button" + }, + { + "emoji": "🉐", + "name": "Japanese “bargain” button" + }, + { + "emoji": "🈹", + "name": "Japanese “discount” button" + }, + { + "emoji": "🈚", + "name": "Japanese “free of charge” button" + }, + { + "emoji": "🈲", + "name": "Japanese “prohibited” button" + }, + { + "emoji": "🉑", + "name": "Japanese “acceptable” button" + }, + { + "emoji": "🈸", + "name": "Japanese “application” button" + }, + { + "emoji": "🈴", + "name": "Japanese “passing grade” button" + }, + { + "emoji": "🈳", + "name": "Japanese “vacancy” button" + }, + { + "emoji": "㊗️", + "name": "Japanese “congratulations” button" + }, + { + "emoji": "㊙️", + "name": "Japanese “secret” button" + }, + { + "emoji": "🈺", + "name": "Japanese “open for business” button" + }, + { + "emoji": "🈵", + "name": "Japanese “no vacancy” button" + }, + { + "emoji": "🔴", + "name": "red circle" + }, + { + "emoji": "🟠", + "name": "orange circle" + }, + { + "emoji": "🟡", + "name": "yellow circle" + }, + { + "emoji": "🟢", + "name": "green circle" + }, + { + "emoji": "🔵", + "name": "blue circle" + }, + { + "emoji": "🟣", + "name": "purple circle" + }, + { + "emoji": "🟤", + "name": "brown circle" + }, + { + "emoji": "⚫", + "name": "black circle" + }, + { + "emoji": "⚪", + "name": "white circle" + }, + { + "emoji": "🟥", + "name": "red square" + }, + { + "emoji": "🟧", + "name": "orange square" + }, + { + "emoji": "🟨", + "name": "yellow square" + }, + { + "emoji": "🟩", + "name": "green square" + }, + { + "emoji": "🟦", + "name": "blue square" + }, + { + "emoji": "🟪", + "name": "purple square" + }, + { + "emoji": "🟫", + "name": "brown square" + }, + { + "emoji": "⬛", + "name": "black large square" + }, + { + "emoji": "⬜", + "name": "white large square" + }, + { + "emoji": "◼️", + "name": "black medium square" + }, + { + "emoji": "◻️", + "name": "white medium square" + }, + { + "emoji": "◾", + "name": "black medium-small square" + }, + { + "emoji": "◽", + "name": "white medium-small square" + }, + { + "emoji": "▪️", + "name": "black small square" + }, + { + "emoji": "▫️", + "name": "white small square" + }, + { + "emoji": "🔶", + "name": "large orange diamond" + }, + { + "emoji": "🔷", + "name": "large blue diamond" + }, + { + "emoji": "🔸", + "name": "small orange diamond" + }, + { + "emoji": "🔹", + "name": "small blue diamond" + }, + { + "emoji": "🔺", + "name": "red triangle pointed up" + }, + { + "emoji": "🔻", + "name": "red triangle pointed down" + }, + { + "emoji": "💠", + "name": "diamond with a dot" + }, + { + "emoji": "🔘", + "name": "radio button" + }, + { + "emoji": "🔳", + "name": "white square button" + }, + { + "emoji": "🔲", + "name": "black square button" + } + ], + "Flags": [ + { + "emoji": "🏁", + "name": "chequered flag" + }, + { + "emoji": "🚩", + "name": "triangular flag" + }, + { + "emoji": "🎌", + "name": "crossed flags" + }, + { + "emoji": "🏴", + "name": "black flag" + }, + { + "emoji": "🏳️", + "name": "white flag" + }, + { + "emoji": "🏳️‍🌈", + "name": "rainbow flag" + }, + { + "emoji": "🏳️‍⚧️", + "name": "transgender flag" + }, + { + "emoji": "🏴‍☠️", + "name": "pirate flag" + }, + { + "emoji": "🇦🇨", + "name": "flag Ascension Island" + }, + { + "emoji": "🇦🇩", + "name": "flag Andorra" + }, + { + "emoji": "🇦🇪", + "name": "flag United Arab Emirates" + }, + { + "emoji": "🇦🇫", + "name": "flag Afghanistan" + }, + { + "emoji": "🇦🇬", + "name": "flag Antigua & Barbuda" + }, + { + "emoji": "🇦🇮", + "name": "flag Anguilla" + }, + { + "emoji": "🇦🇱", + "name": "flag Albania" + }, + { + "emoji": "🇦🇲", + "name": "flag Armenia" + }, + { + "emoji": "🇦🇴", + "name": "flag Angola" + }, + { + "emoji": "🇦🇶", + "name": "flag Antarctica" + }, + { + "emoji": "🇦🇷", + "name": "flag Argentina" + }, + { + "emoji": "🇦🇸", + "name": "flag American Samoa" + }, + { + "emoji": "🇦🇹", + "name": "flag Austria" + }, + { + "emoji": "🇦🇺", + "name": "flag Australia" + }, + { + "emoji": "🇦🇼", + "name": "flag Aruba" + }, + { + "emoji": "🇦🇽", + "name": "flag Åland Islands" + }, + { + "emoji": "🇦🇿", + "name": "flag Azerbaijan" + }, + { + "emoji": "🇧🇦", + "name": "flag Bosnia & Herzegovina" + }, + { + "emoji": "🇧🇧", + "name": "flag Barbados" + }, + { + "emoji": "🇧🇩", + "name": "flag Bangladesh" + }, + { + "emoji": "🇧🇪", + "name": "flag Belgium" + }, + { + "emoji": "🇧🇫", + "name": "flag Burkina Faso" + }, + { + "emoji": "🇧🇬", + "name": "flag Bulgaria" + }, + { + "emoji": "🇧🇭", + "name": "flag Bahrain" + }, + { + "emoji": "🇧🇮", + "name": "flag Burundi" + }, + { + "emoji": "🇧🇯", + "name": "flag Benin" + }, + { + "emoji": "🇧🇱", + "name": "flag St. Barthélemy" + }, + { + "emoji": "🇧🇲", + "name": "flag Bermuda" + }, + { + "emoji": "🇧🇳", + "name": "flag Brunei" + }, + { + "emoji": "🇧🇴", + "name": "flag Bolivia" + }, + { + "emoji": "🇧🇶", + "name": "flag Caribbean Netherlands" + }, + { + "emoji": "🇧🇷", + "name": "flag Brazil" + }, + { + "emoji": "🇧🇸", + "name": "flag Bahamas" + }, + { + "emoji": "🇧🇹", + "name": "flag Bhutan" + }, + { + "emoji": "🇧🇻", + "name": "flag Bouvet Island" + }, + { + "emoji": "🇧🇼", + "name": "flag Botswana" + }, + { + "emoji": "🇧🇾", + "name": "flag Belarus" + }, + { + "emoji": "🇧🇿", + "name": "flag Belize" + }, + { + "emoji": "🇨🇦", + "name": "flag Canada" + }, + { + "emoji": "🇨🇨", + "name": "flag Cocos (Keeling) Islands" + }, + { + "emoji": "🇨🇩", + "name": "flag Congo - Kinshasa" + }, + { + "emoji": "🇨🇫", + "name": "flag Central African Republic" + }, + { + "emoji": "🇨🇬", + "name": "flag Congo - Brazzaville" + }, + { + "emoji": "🇨🇭", + "name": "flag Switzerland" + }, + { + "emoji": "🇨🇮", + "name": "flag Côte d’Ivoire" + }, + { + "emoji": "🇨🇰", + "name": "flag Cook Islands" + }, + { + "emoji": "🇨🇱", + "name": "flag Chile" + }, + { + "emoji": "🇨🇲", + "name": "flag Cameroon" + }, + { + "emoji": "🇨🇳", + "name": "flag China" + }, + { + "emoji": "🇨🇴", + "name": "flag Colombia" + }, + { + "emoji": "🇨🇵", + "name": "flag Clipperton Island" + }, + { + "emoji": "🇨🇷", + "name": "flag Costa Rica" + }, + { + "emoji": "🇨🇺", + "name": "flag Cuba" + }, + { + "emoji": "🇨🇻", + "name": "flag Cape Verde" + }, + { + "emoji": "🇨🇼", + "name": "flag Curaçao" + }, + { + "emoji": "🇨🇽", + "name": "flag Christmas Island" + }, + { + "emoji": "🇨🇾", + "name": "flag Cyprus" + }, + { + "emoji": "🇨🇿", + "name": "flag Czechia" + }, + { + "emoji": "🇩🇪", + "name": "flag Germany" + }, + { + "emoji": "🇩🇬", + "name": "flag Diego Garcia" + }, + { + "emoji": "🇩🇯", + "name": "flag Djibouti" + }, + { + "emoji": "🇩🇰", + "name": "flag Denmark" + }, + { + "emoji": "🇩🇲", + "name": "flag Dominica" + }, + { + "emoji": "🇩🇴", + "name": "flag Dominican Republic" + }, + { + "emoji": "🇩🇿", + "name": "flag Algeria" + }, + { + "emoji": "🇪🇦", + "name": "flag Ceuta & Melilla" + }, + { + "emoji": "🇪🇨", + "name": "flag Ecuador" + }, + { + "emoji": "🇪🇪", + "name": "flag Estonia" + }, + { + "emoji": "🇪🇬", + "name": "flag Egypt" + }, + { + "emoji": "🇪🇭", + "name": "flag Western Sahara" + }, + { + "emoji": "🇪🇷", + "name": "flag Eritrea" + }, + { + "emoji": "🇪🇸", + "name": "flag Spain" + }, + { + "emoji": "🇪🇹", + "name": "flag Ethiopia" + }, + { + "emoji": "🇪🇺", + "name": "flag European Union" + }, + { + "emoji": "🇫🇮", + "name": "flag Finland" + }, + { + "emoji": "🇫🇯", + "name": "flag Fiji" + }, + { + "emoji": "🇫🇰", + "name": "flag Falkland Islands" + }, + { + "emoji": "🇫🇲", + "name": "flag Micronesia" + }, + { + "emoji": "🇫🇴", + "name": "flag Faroe Islands" + }, + { + "emoji": "🇫🇷", + "name": "flag France" + }, + { + "emoji": "🇬🇦", + "name": "flag Gabon" + }, + { + "emoji": "🇬🇧", + "name": "flag United Kingdom" + }, + { + "emoji": "🇬🇩", + "name": "flag Grenada" + }, + { + "emoji": "🇬🇪", + "name": "flag Georgia" + }, + { + "emoji": "🇬🇫", + "name": "flag French Guiana" + }, + { + "emoji": "🇬🇬", + "name": "flag Guernsey" + }, + { + "emoji": "🇬🇭", + "name": "flag Ghana" + }, + { + "emoji": "🇬🇮", + "name": "flag Gibraltar" + }, + { + "emoji": "🇬🇱", + "name": "flag Greenland" + }, + { + "emoji": "🇬🇲", + "name": "flag Gambia" + }, + { + "emoji": "🇬🇳", + "name": "flag Guinea" + }, + { + "emoji": "🇬🇵", + "name": "flag Guadeloupe" + }, + { + "emoji": "🇬🇶", + "name": "flag Equatorial Guinea" + }, + { + "emoji": "🇬🇷", + "name": "flag Greece" + }, + { + "emoji": "🇬🇸", + "name": "flag South Georgia & South Sandwich Islands" + }, + { + "emoji": "🇬🇹", + "name": "flag Guatemala" + }, + { + "emoji": "🇬🇺", + "name": "flag Guam" + }, + { + "emoji": "🇬🇼", + "name": "flag Guinea-Bissau" + }, + { + "emoji": "🇬🇾", + "name": "flag Guyana" + }, + { + "emoji": "🇭🇰", + "name": "flag Hong Kong SAR China" + }, + { + "emoji": "🇭🇲", + "name": "flag Heard & McDonald Islands" + }, + { + "emoji": "🇭🇳", + "name": "flag Honduras" + }, + { + "emoji": "🇭🇷", + "name": "flag Croatia" + }, + { + "emoji": "🇭🇹", + "name": "flag Haiti" + }, + { + "emoji": "🇭🇺", + "name": "flag Hungary" + }, + { + "emoji": "🇮🇨", + "name": "flag Canary Islands" + }, + { + "emoji": "🇮🇩", + "name": "flag Indonesia" + }, + { + "emoji": "🇮🇪", + "name": "flag Ireland" + }, + { + "emoji": "🇮🇱", + "name": "flag Israel" + }, + { + "emoji": "🇮🇲", + "name": "flag Isle of Man" + }, + { + "emoji": "🇮🇳", + "name": "flag India" + }, + { + "emoji": "🇮🇴", + "name": "flag British Indian Ocean Territory" + }, + { + "emoji": "🇮🇶", + "name": "flag Iraq" + }, + { + "emoji": "🇮🇷", + "name": "flag Iran" + }, + { + "emoji": "🇮🇸", + "name": "flag Iceland" + }, + { + "emoji": "🇮🇹", + "name": "flag Italy" + }, + { + "emoji": "🇯🇪", + "name": "flag Jersey" + }, + { + "emoji": "🇯🇲", + "name": "flag Jamaica" + }, + { + "emoji": "🇯🇴", + "name": "flag Jordan" + }, + { + "emoji": "🇯🇵", + "name": "flag Japan" + }, + { + "emoji": "🇰🇪", + "name": "flag Kenya" + }, + { + "emoji": "🇰🇬", + "name": "flag Kyrgyzstan" + }, + { + "emoji": "🇰🇭", + "name": "flag Cambodia" + }, + { + "emoji": "🇰🇮", + "name": "flag Kiribati" + }, + { + "emoji": "🇰🇲", + "name": "flag Comoros" + }, + { + "emoji": "🇰🇳", + "name": "flag St. Kitts & Nevis" + }, + { + "emoji": "🇰🇵", + "name": "flag North Korea" + }, + { + "emoji": "🇰🇷", + "name": "flag South Korea" + }, + { + "emoji": "🇰🇼", + "name": "flag Kuwait" + }, + { + "emoji": "🇰🇾", + "name": "flag Cayman Islands" + }, + { + "emoji": "🇰🇿", + "name": "flag Kazakhstan" + }, + { + "emoji": "🇱🇦", + "name": "flag Laos" + }, + { + "emoji": "🇱🇧", + "name": "flag Lebanon" + }, + { + "emoji": "🇱🇨", + "name": "flag St. Lucia" + }, + { + "emoji": "🇱🇮", + "name": "flag Liechtenstein" + }, + { + "emoji": "🇱🇰", + "name": "flag Sri Lanka" + }, + { + "emoji": "🇱🇷", + "name": "flag Liberia" + }, + { + "emoji": "🇱🇸", + "name": "flag Lesotho" + }, + { + "emoji": "🇱🇹", + "name": "flag Lithuania" + }, + { + "emoji": "🇱🇺", + "name": "flag Luxembourg" + }, + { + "emoji": "🇱🇻", + "name": "flag Latvia" + }, + { + "emoji": "🇱🇾", + "name": "flag Libya" + }, + { + "emoji": "🇲🇦", + "name": "flag Morocco" + }, + { + "emoji": "🇲🇨", + "name": "flag Monaco" + }, + { + "emoji": "🇲🇩", + "name": "flag Moldova" + }, + { + "emoji": "🇲🇪", + "name": "flag Montenegro" + }, + { + "emoji": "🇲🇫", + "name": "flag St. Martin" + }, + { + "emoji": "🇲🇬", + "name": "flag Madagascar" + }, + { + "emoji": "🇲🇭", + "name": "flag Marshall Islands" + }, + { + "emoji": "🇲🇰", + "name": "flag North Macedonia" + }, + { + "emoji": "🇲🇱", + "name": "flag Mali" + }, + { + "emoji": "🇲🇲", + "name": "flag Myanmar (Burma)" + }, + { + "emoji": "🇲🇳", + "name": "flag Mongolia" + }, + { + "emoji": "🇲🇴", + "name": "flag Macao SAR China" + }, + { + "emoji": "🇲🇵", + "name": "flag Northern Mariana Islands" + }, + { + "emoji": "🇲🇶", + "name": "flag Martinique" + }, + { + "emoji": "🇲🇷", + "name": "flag Mauritania" + }, + { + "emoji": "🇲🇸", + "name": "flag Montserrat" + }, + { + "emoji": "🇲🇹", + "name": "flag Malta" + }, + { + "emoji": "🇲🇺", + "name": "flag Mauritius" + }, + { + "emoji": "🇲🇻", + "name": "flag Maldives" + }, + { + "emoji": "🇲🇼", + "name": "flag Malawi" + }, + { + "emoji": "🇲🇽", + "name": "flag Mexico" + }, + { + "emoji": "🇲🇾", + "name": "flag Malaysia" + }, + { + "emoji": "🇲🇿", + "name": "flag Mozambique" + }, + { + "emoji": "🇳🇦", + "name": "flag Namibia" + }, + { + "emoji": "🇳🇨", + "name": "flag New Caledonia" + }, + { + "emoji": "🇳🇪", + "name": "flag Niger" + }, + { + "emoji": "🇳🇫", + "name": "flag Norfolk Island" + }, + { + "emoji": "🇳🇬", + "name": "flag Nigeria" + }, + { + "emoji": "🇳🇮", + "name": "flag Nicaragua" + }, + { + "emoji": "🇳🇱", + "name": "flag Netherlands" + }, + { + "emoji": "🇳🇴", + "name": "flag Norway" + }, + { + "emoji": "🇳🇵", + "name": "flag Nepal" + }, + { + "emoji": "🇳🇷", + "name": "flag Nauru" + }, + { + "emoji": "🇳🇺", + "name": "flag Niue" + }, + { + "emoji": "🇳🇿", + "name": "flag New Zealand" + }, + { + "emoji": "🇴🇲", + "name": "flag Oman" + }, + { + "emoji": "🇵🇦", + "name": "flag Panama" + }, + { + "emoji": "🇵🇪", + "name": "flag Peru" + }, + { + "emoji": "🇵🇫", + "name": "flag French Polynesia" + }, + { + "emoji": "🇵🇬", + "name": "flag Papua New Guinea" + }, + { + "emoji": "🇵🇭", + "name": "flag Philippines" + }, + { + "emoji": "🇵🇰", + "name": "flag Pakistan" + }, + { + "emoji": "🇵🇱", + "name": "flag Poland" + }, + { + "emoji": "🇵🇲", + "name": "flag St. Pierre & Miquelon" + }, + { + "emoji": "🇵🇳", + "name": "flag Pitcairn Islands" + }, + { + "emoji": "🇵🇷", + "name": "flag Puerto Rico" + }, + { + "emoji": "🇵🇸", + "name": "flag Palestinian Territories" + }, + { + "emoji": "🇵🇹", + "name": "flag Portugal" + }, + { + "emoji": "🇵🇼", + "name": "flag Palau" + }, + { + "emoji": "🇵🇾", + "name": "flag Paraguay" + }, + { + "emoji": "🇶🇦", + "name": "flag Qatar" + }, + { + "emoji": "🇷🇪", + "name": "flag Réunion" + }, + { + "emoji": "🇷🇴", + "name": "flag Romania" + }, + { + "emoji": "🇷🇸", + "name": "flag Serbia" + }, + { + "emoji": "🇷🇺", + "name": "flag Russia" + }, + { + "emoji": "🇷🇼", + "name": "flag Rwanda" + }, + { + "emoji": "🇸🇦", + "name": "flag Saudi Arabia" + }, + { + "emoji": "🇸🇧", + "name": "flag Solomon Islands" + }, + { + "emoji": "🇸🇨", + "name": "flag Seychelles" + }, + { + "emoji": "🇸🇩", + "name": "flag Sudan" + }, + { + "emoji": "🇸🇪", + "name": "flag Sweden" + }, + { + "emoji": "🇸🇬", + "name": "flag Singapore" + }, + { + "emoji": "🇸🇭", + "name": "flag St. Helena" + }, + { + "emoji": "🇸🇮", + "name": "flag Slovenia" + }, + { + "emoji": "🇸🇯", + "name": "flag Svalbard & Jan Mayen" + }, + { + "emoji": "🇸🇰", + "name": "flag Slovakia" + }, + { + "emoji": "🇸🇱", + "name": "flag Sierra Leone" + }, + { + "emoji": "🇸🇲", + "name": "flag San Marino" + }, + { + "emoji": "🇸🇳", + "name": "flag Senegal" + }, + { + "emoji": "🇸🇴", + "name": "flag Somalia" + }, + { + "emoji": "🇸🇷", + "name": "flag Suriname" + }, + { + "emoji": "🇸🇸", + "name": "flag South Sudan" + }, + { + "emoji": "🇸🇹", + "name": "flag São Tomé & Príncipe" + }, + { + "emoji": "🇸🇻", + "name": "flag El Salvador" + }, + { + "emoji": "🇸🇽", + "name": "flag Sint Maarten" + }, + { + "emoji": "🇸🇾", + "name": "flag Syria" + }, + { + "emoji": "🇸🇿", + "name": "flag Eswatini" + }, + { + "emoji": "🇹🇦", + "name": "flag Tristan da Cunha" + }, + { + "emoji": "🇹🇨", + "name": "flag Turks & Caicos Islands" + }, + { + "emoji": "🇹🇩", + "name": "flag Chad" + }, + { + "emoji": "🇹🇫", + "name": "flag French Southern Territories" + }, + { + "emoji": "🇹🇬", + "name": "flag Togo" + }, + { + "emoji": "🇹🇭", + "name": "flag Thailand" + }, + { + "emoji": "🇹🇯", + "name": "flag Tajikistan" + }, + { + "emoji": "🇹🇰", + "name": "flag Tokelau" + }, + { + "emoji": "🇹🇱", + "name": "flag Timor-Leste" + }, + { + "emoji": "🇹🇲", + "name": "flag Turkmenistan" + }, + { + "emoji": "🇹🇳", + "name": "flag Tunisia" + }, + { + "emoji": "🇹🇴", + "name": "flag Tonga" + }, + { + "emoji": "🇹🇷", + "name": "flag Türkiye" + }, + { + "emoji": "🇹🇹", + "name": "flag Trinidad & Tobago" + }, + { + "emoji": "🇹🇻", + "name": "flag Tuvalu" + }, + { + "emoji": "🇹🇼", + "name": "flag Taiwan" + }, + { + "emoji": "🇹🇿", + "name": "flag Tanzania" + }, + { + "emoji": "🇺🇦", + "name": "flag Ukraine" + }, + { + "emoji": "🇺🇬", + "name": "flag Uganda" + }, + { + "emoji": "🇺🇲", + "name": "flag U.S. Outlying Islands" + }, + { + "emoji": "🇺🇳", + "name": "flag United Nations" + }, + { + "emoji": "🇺🇸", + "name": "flag United States" + }, + { + "emoji": "🇺🇾", + "name": "flag Uruguay" + }, + { + "emoji": "🇺🇿", + "name": "flag Uzbekistan" + }, + { + "emoji": "🇻🇦", + "name": "flag Vatican City" + }, + { + "emoji": "🇻🇨", + "name": "flag St. Vincent & Grenadines" + }, + { + "emoji": "🇻🇪", + "name": "flag Venezuela" + }, + { + "emoji": "🇻🇬", + "name": "flag British Virgin Islands" + }, + { + "emoji": "🇻🇮", + "name": "flag U.S. Virgin Islands" + }, + { + "emoji": "🇻🇳", + "name": "flag Vietnam" + }, + { + "emoji": "🇻🇺", + "name": "flag Vanuatu" + }, + { + "emoji": "🇼🇫", + "name": "flag Wallis & Futuna" + }, + { + "emoji": "🇼🇸", + "name": "flag Samoa" + }, + { + "emoji": "🇽🇰", + "name": "flag Kosovo" + }, + { + "emoji": "🇾🇪", + "name": "flag Yemen" + }, + { + "emoji": "🇾🇹", + "name": "flag Mayotte" + }, + { + "emoji": "🇿🇦", + "name": "flag South Africa" + }, + { + "emoji": "🇿🇲", + "name": "flag Zambia" + }, + { + "emoji": "🇿🇼", + "name": "flag Zimbabwe" + }, + { + "emoji": "🏴󠁧󠁢󠁥󠁮󠁧󠁿", + "name": "flag England" + }, + { + "emoji": "🏴󠁧󠁢󠁳󠁣󠁴󠁿", + "name": "flag Scotland" + }, + { + "emoji": "🏴󠁧󠁢󠁷󠁬󠁳󠁿", + "name": "flag Wales" + } + ] +} \ No newline at end of file diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 0000000..500d424 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000..500d424 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/fonts/inter/Inter-italic.var.woff2 b/public/fonts/inter/Inter-italic.var.woff2 new file mode 100644 index 0000000..0387531 Binary files /dev/null and b/public/fonts/inter/Inter-italic.var.woff2 differ diff --git a/public/fonts/inter/Inter-roman.var.woff2 b/public/fonts/inter/Inter-roman.var.woff2 new file mode 100644 index 0000000..a6efdc4 Binary files /dev/null and b/public/fonts/inter/Inter-roman.var.woff2 differ diff --git a/public/fonts/inter/inter.css b/public/fonts/inter/inter.css new file mode 100644 index 0000000..3294b01 --- /dev/null +++ b/public/fonts/inter/inter.css @@ -0,0 +1,17 @@ +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: normal; + font-named-instance: 'Regular'; + src: url("Inter-roman.var.woff2?v=3.18") format("woff2"); +} + +@font-face { + font-family: 'Inter var'; + font-weight: 100 900; + font-display: swap; + font-style: italic; + font-named-instance: 'Italic'; + src: url("Inter-italic.var.woff2?v=3.18") format("woff2"); +} diff --git a/public/i18n/en.json b/public/i18n/en.json new file mode 100644 index 0000000..66a901c --- /dev/null +++ b/public/i18n/en.json @@ -0,0 +1,5 @@ +{ + "welcome-back": "Welcome back", + "Project": "Project", + "Analytics": "Analytics" +} diff --git a/public/icons/feather.svg b/public/icons/feather.svg new file mode 100644 index 0000000..e74e162 --- /dev/null +++ b/public/icons/feather.svg @@ -0,0 +1,4309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/heroicons-mini.svg b/public/icons/heroicons-mini.svg new file mode 100644 index 0000000..d860c88 --- /dev/null +++ b/public/icons/heroicons-mini.svg @@ -0,0 +1,940 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/heroicons-outline.svg b/public/icons/heroicons-outline.svg new file mode 100644 index 0000000..7f337e3 --- /dev/null +++ b/public/icons/heroicons-outline.svg @@ -0,0 +1,893 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/heroicons-solid.svg b/public/icons/heroicons-solid.svg new file mode 100644 index 0000000..c669d9b --- /dev/null +++ b/public/icons/heroicons-solid.svg @@ -0,0 +1,958 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/icons/material-outline.svg b/public/icons/material-outline.svg new file mode 100644 index 0000000..d378c92 --- /dev/null +++ b/public/icons/material-outline.svg @@ -0,0 +1,3586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/material-solid.svg b/public/icons/material-solid.svg new file mode 100644 index 0000000..b8c8fe3 --- /dev/null +++ b/public/icons/material-solid.svg @@ -0,0 +1,3586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Asset 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ic_edit_off_24px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ic_recommend_24px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +ic_dialer_rtt_revised_24px + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/material-twotone.svg b/public/icons/material-twotone.svg new file mode 100644 index 0000000..70f1814 --- /dev/null +++ b/public/icons/material-twotone.svg @@ -0,0 +1,3586 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/images/apps/contacts/flags.png b/public/images/apps/contacts/flags.png new file mode 100644 index 0000000..7fdb59b Binary files /dev/null and b/public/images/apps/contacts/flags.png differ diff --git a/public/images/apps/ecommerce/products/watch-01-01.jpg b/public/images/apps/ecommerce/products/watch-01-01.jpg new file mode 100644 index 0000000..0c94610 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-01-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-01-02.jpg b/public/images/apps/ecommerce/products/watch-01-02.jpg new file mode 100644 index 0000000..1c9f218 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-01-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-01-03.jpg b/public/images/apps/ecommerce/products/watch-01-03.jpg new file mode 100644 index 0000000..53ddc11 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-01-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-01-thumb.jpg b/public/images/apps/ecommerce/products/watch-01-thumb.jpg new file mode 100644 index 0000000..5e5c15a Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-01-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-02-01.jpg b/public/images/apps/ecommerce/products/watch-02-01.jpg new file mode 100644 index 0000000..606b4ae Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-02-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-02-02.jpg b/public/images/apps/ecommerce/products/watch-02-02.jpg new file mode 100644 index 0000000..6f085bb Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-02-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-02-03.jpg b/public/images/apps/ecommerce/products/watch-02-03.jpg new file mode 100644 index 0000000..b9ea798 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-02-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-02-thumb.jpg b/public/images/apps/ecommerce/products/watch-02-thumb.jpg new file mode 100644 index 0000000..09d2085 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-02-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-03-01.jpg b/public/images/apps/ecommerce/products/watch-03-01.jpg new file mode 100644 index 0000000..eb93d9d Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-03-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-03-02.jpg b/public/images/apps/ecommerce/products/watch-03-02.jpg new file mode 100644 index 0000000..8e3f007 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-03-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-03-03.jpg b/public/images/apps/ecommerce/products/watch-03-03.jpg new file mode 100644 index 0000000..11a992c Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-03-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-03-thumb.jpg b/public/images/apps/ecommerce/products/watch-03-thumb.jpg new file mode 100644 index 0000000..b398b9c Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-03-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-04-01.jpg b/public/images/apps/ecommerce/products/watch-04-01.jpg new file mode 100644 index 0000000..bea6491 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-04-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-04-02.jpg b/public/images/apps/ecommerce/products/watch-04-02.jpg new file mode 100644 index 0000000..4441d0a Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-04-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-04-03.jpg b/public/images/apps/ecommerce/products/watch-04-03.jpg new file mode 100644 index 0000000..8ae9224 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-04-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-04-thumb.jpg b/public/images/apps/ecommerce/products/watch-04-thumb.jpg new file mode 100644 index 0000000..ed636f3 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-04-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-05-01.jpg b/public/images/apps/ecommerce/products/watch-05-01.jpg new file mode 100644 index 0000000..e1d2f42 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-05-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-05-02.jpg b/public/images/apps/ecommerce/products/watch-05-02.jpg new file mode 100644 index 0000000..586819b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-05-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-05-03.jpg b/public/images/apps/ecommerce/products/watch-05-03.jpg new file mode 100644 index 0000000..89fa528 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-05-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-05-thumb.jpg b/public/images/apps/ecommerce/products/watch-05-thumb.jpg new file mode 100644 index 0000000..dc8da52 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-05-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-06-01.jpg b/public/images/apps/ecommerce/products/watch-06-01.jpg new file mode 100644 index 0000000..3b3a804 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-06-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-06-02.jpg b/public/images/apps/ecommerce/products/watch-06-02.jpg new file mode 100644 index 0000000..23110a5 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-06-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-06-03.jpg b/public/images/apps/ecommerce/products/watch-06-03.jpg new file mode 100644 index 0000000..8e6db6e Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-06-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-06-thumb.jpg b/public/images/apps/ecommerce/products/watch-06-thumb.jpg new file mode 100644 index 0000000..b759753 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-06-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-07-01.jpg b/public/images/apps/ecommerce/products/watch-07-01.jpg new file mode 100644 index 0000000..347e057 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-07-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-07-02.jpg b/public/images/apps/ecommerce/products/watch-07-02.jpg new file mode 100644 index 0000000..d59772a Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-07-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-07-03.jpg b/public/images/apps/ecommerce/products/watch-07-03.jpg new file mode 100644 index 0000000..4e15ba8 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-07-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-07-thumb.jpg b/public/images/apps/ecommerce/products/watch-07-thumb.jpg new file mode 100644 index 0000000..2753ad6 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-07-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-08-01.jpg b/public/images/apps/ecommerce/products/watch-08-01.jpg new file mode 100644 index 0000000..c81aefc Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-08-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-08-02.jpg b/public/images/apps/ecommerce/products/watch-08-02.jpg new file mode 100644 index 0000000..335fbb4 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-08-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-08-03.jpg b/public/images/apps/ecommerce/products/watch-08-03.jpg new file mode 100644 index 0000000..1e3162b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-08-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-08-thumb.jpg b/public/images/apps/ecommerce/products/watch-08-thumb.jpg new file mode 100644 index 0000000..d4ce5cb Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-08-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-09-01.jpg b/public/images/apps/ecommerce/products/watch-09-01.jpg new file mode 100644 index 0000000..eba97a1 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-09-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-09-02.jpg b/public/images/apps/ecommerce/products/watch-09-02.jpg new file mode 100644 index 0000000..4e390d7 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-09-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-09-03.jpg b/public/images/apps/ecommerce/products/watch-09-03.jpg new file mode 100644 index 0000000..bd17083 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-09-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-09-thumb.jpg b/public/images/apps/ecommerce/products/watch-09-thumb.jpg new file mode 100644 index 0000000..79e8287 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-09-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-10-01.jpg b/public/images/apps/ecommerce/products/watch-10-01.jpg new file mode 100644 index 0000000..2bc0721 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-10-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-10-02.jpg b/public/images/apps/ecommerce/products/watch-10-02.jpg new file mode 100644 index 0000000..7105693 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-10-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-10-03.jpg b/public/images/apps/ecommerce/products/watch-10-03.jpg new file mode 100644 index 0000000..342ec79 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-10-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-10-thumb.jpg b/public/images/apps/ecommerce/products/watch-10-thumb.jpg new file mode 100644 index 0000000..9a5ce1b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-10-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-11-01.jpg b/public/images/apps/ecommerce/products/watch-11-01.jpg new file mode 100644 index 0000000..f539cb1 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-11-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-11-02.jpg b/public/images/apps/ecommerce/products/watch-11-02.jpg new file mode 100644 index 0000000..13e378b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-11-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-11-03.jpg b/public/images/apps/ecommerce/products/watch-11-03.jpg new file mode 100644 index 0000000..4d38f3a Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-11-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-11-thumb.jpg b/public/images/apps/ecommerce/products/watch-11-thumb.jpg new file mode 100644 index 0000000..bfdfee0 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-11-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-12-01.jpg b/public/images/apps/ecommerce/products/watch-12-01.jpg new file mode 100644 index 0000000..27fa31b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-12-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-12-02.jpg b/public/images/apps/ecommerce/products/watch-12-02.jpg new file mode 100644 index 0000000..1618133 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-12-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-12-03.jpg b/public/images/apps/ecommerce/products/watch-12-03.jpg new file mode 100644 index 0000000..25340ee Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-12-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-12-thumb.jpg b/public/images/apps/ecommerce/products/watch-12-thumb.jpg new file mode 100644 index 0000000..dfa4d5c Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-12-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-13-01.jpg b/public/images/apps/ecommerce/products/watch-13-01.jpg new file mode 100644 index 0000000..386ea65 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-13-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-13-02.jpg b/public/images/apps/ecommerce/products/watch-13-02.jpg new file mode 100644 index 0000000..2b1c8a5 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-13-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-13-03.jpg b/public/images/apps/ecommerce/products/watch-13-03.jpg new file mode 100644 index 0000000..979b534 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-13-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-13-thumb.jpg b/public/images/apps/ecommerce/products/watch-13-thumb.jpg new file mode 100644 index 0000000..1986dc6 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-13-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-14-01.jpg b/public/images/apps/ecommerce/products/watch-14-01.jpg new file mode 100644 index 0000000..007c8bb Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-14-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-14-02.jpg b/public/images/apps/ecommerce/products/watch-14-02.jpg new file mode 100644 index 0000000..bbd193b Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-14-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-14-03.jpg b/public/images/apps/ecommerce/products/watch-14-03.jpg new file mode 100644 index 0000000..7e7737f Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-14-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-14-thumb.jpg b/public/images/apps/ecommerce/products/watch-14-thumb.jpg new file mode 100644 index 0000000..85ef848 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-14-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-15-01.jpg b/public/images/apps/ecommerce/products/watch-15-01.jpg new file mode 100644 index 0000000..83263ab Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-15-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-15-02.jpg b/public/images/apps/ecommerce/products/watch-15-02.jpg new file mode 100644 index 0000000..c52a8e3 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-15-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-15-03.jpg b/public/images/apps/ecommerce/products/watch-15-03.jpg new file mode 100644 index 0000000..8eb9d40 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-15-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-15-thumb.jpg b/public/images/apps/ecommerce/products/watch-15-thumb.jpg new file mode 100644 index 0000000..434be54 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-15-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-16-01.jpg b/public/images/apps/ecommerce/products/watch-16-01.jpg new file mode 100644 index 0000000..3fdeb6e Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-16-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-16-02.jpg b/public/images/apps/ecommerce/products/watch-16-02.jpg new file mode 100644 index 0000000..8c824aa Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-16-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-16-03.jpg b/public/images/apps/ecommerce/products/watch-16-03.jpg new file mode 100644 index 0000000..a10ae70 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-16-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-16-thumb.jpg b/public/images/apps/ecommerce/products/watch-16-thumb.jpg new file mode 100644 index 0000000..b4f4845 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-16-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-17-01.jpg b/public/images/apps/ecommerce/products/watch-17-01.jpg new file mode 100644 index 0000000..73d8448 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-17-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-17-02.jpg b/public/images/apps/ecommerce/products/watch-17-02.jpg new file mode 100644 index 0000000..0cdd231 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-17-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-17-03.jpg b/public/images/apps/ecommerce/products/watch-17-03.jpg new file mode 100644 index 0000000..fb1d7f4 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-17-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-17-thumb.jpg b/public/images/apps/ecommerce/products/watch-17-thumb.jpg new file mode 100644 index 0000000..621d7c6 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-17-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-18-01.jpg b/public/images/apps/ecommerce/products/watch-18-01.jpg new file mode 100644 index 0000000..75dad60 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-18-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-18-02.jpg b/public/images/apps/ecommerce/products/watch-18-02.jpg new file mode 100644 index 0000000..5a2fe10 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-18-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-18-03.jpg b/public/images/apps/ecommerce/products/watch-18-03.jpg new file mode 100644 index 0000000..ea905cf Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-18-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-18-thumb.jpg b/public/images/apps/ecommerce/products/watch-18-thumb.jpg new file mode 100644 index 0000000..5746009 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-18-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-19-01.jpg b/public/images/apps/ecommerce/products/watch-19-01.jpg new file mode 100644 index 0000000..36e7987 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-19-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-19-02.jpg b/public/images/apps/ecommerce/products/watch-19-02.jpg new file mode 100644 index 0000000..b00e715 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-19-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-19-03.jpg b/public/images/apps/ecommerce/products/watch-19-03.jpg new file mode 100644 index 0000000..68f7e38 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-19-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-19-thumb.jpg b/public/images/apps/ecommerce/products/watch-19-thumb.jpg new file mode 100644 index 0000000..18b5811 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-19-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-20-01.jpg b/public/images/apps/ecommerce/products/watch-20-01.jpg new file mode 100644 index 0000000..26f6248 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-20-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-20-02.jpg b/public/images/apps/ecommerce/products/watch-20-02.jpg new file mode 100644 index 0000000..f530af9 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-20-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-20-03.jpg b/public/images/apps/ecommerce/products/watch-20-03.jpg new file mode 100644 index 0000000..ada08be Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-20-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-20-thumb.jpg b/public/images/apps/ecommerce/products/watch-20-thumb.jpg new file mode 100644 index 0000000..a53cbf9 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-20-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-21-01.jpg b/public/images/apps/ecommerce/products/watch-21-01.jpg new file mode 100644 index 0000000..9921ec2 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-21-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-21-02.jpg b/public/images/apps/ecommerce/products/watch-21-02.jpg new file mode 100644 index 0000000..4c5268d Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-21-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-21-03.jpg b/public/images/apps/ecommerce/products/watch-21-03.jpg new file mode 100644 index 0000000..dedefed Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-21-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-21-thumb.jpg b/public/images/apps/ecommerce/products/watch-21-thumb.jpg new file mode 100644 index 0000000..d05a463 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-21-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-22-01.jpg b/public/images/apps/ecommerce/products/watch-22-01.jpg new file mode 100644 index 0000000..d4a4308 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-22-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-22-02.jpg b/public/images/apps/ecommerce/products/watch-22-02.jpg new file mode 100644 index 0000000..5103ded Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-22-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-22-03.jpg b/public/images/apps/ecommerce/products/watch-22-03.jpg new file mode 100644 index 0000000..69a59e2 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-22-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-22-thumb.jpg b/public/images/apps/ecommerce/products/watch-22-thumb.jpg new file mode 100644 index 0000000..4c0bbc0 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-22-thumb.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-23-01.jpg b/public/images/apps/ecommerce/products/watch-23-01.jpg new file mode 100644 index 0000000..8b14d6a Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-23-01.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-23-02.jpg b/public/images/apps/ecommerce/products/watch-23-02.jpg new file mode 100644 index 0000000..d383d7e Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-23-02.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-23-03.jpg b/public/images/apps/ecommerce/products/watch-23-03.jpg new file mode 100644 index 0000000..c241ce7 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-23-03.jpg differ diff --git a/public/images/apps/ecommerce/products/watch-23-thumb.jpg b/public/images/apps/ecommerce/products/watch-23-thumb.jpg new file mode 100644 index 0000000..f423970 Binary files /dev/null and b/public/images/apps/ecommerce/products/watch-23-thumb.jpg differ diff --git a/public/images/apps/mailbox/birds-eye-sydney_preview.jpg b/public/images/apps/mailbox/birds-eye-sydney_preview.jpg new file mode 100644 index 0000000..434d5a2 Binary files /dev/null and b/public/images/apps/mailbox/birds-eye-sydney_preview.jpg differ diff --git a/public/images/apps/mailbox/lake-of-carrezza_preview.png b/public/images/apps/mailbox/lake-of-carrezza_preview.png new file mode 100644 index 0000000..8ae3d25 Binary files /dev/null and b/public/images/apps/mailbox/lake-of-carrezza_preview.png differ diff --git a/public/images/apps/mailbox/mystery-forest_preview.jpg b/public/images/apps/mailbox/mystery-forest_preview.jpg new file mode 100644 index 0000000..4afe9bd Binary files /dev/null and b/public/images/apps/mailbox/mystery-forest_preview.jpg differ diff --git a/public/images/apps/mailbox/yosemite-national-park_preview.png b/public/images/apps/mailbox/yosemite-national-park_preview.png new file mode 100644 index 0000000..3ec32ab Binary files /dev/null and b/public/images/apps/mailbox/yosemite-national-park_preview.png differ diff --git a/public/images/avatars/female-01.jpg b/public/images/avatars/female-01.jpg new file mode 100644 index 0000000..7b03905 Binary files /dev/null and b/public/images/avatars/female-01.jpg differ diff --git a/public/images/avatars/female-02.jpg b/public/images/avatars/female-02.jpg new file mode 100644 index 0000000..a112c3b Binary files /dev/null and b/public/images/avatars/female-02.jpg differ diff --git a/public/images/avatars/female-03.jpg b/public/images/avatars/female-03.jpg new file mode 100644 index 0000000..65e455b Binary files /dev/null and b/public/images/avatars/female-03.jpg differ diff --git a/public/images/avatars/female-04.jpg b/public/images/avatars/female-04.jpg new file mode 100644 index 0000000..9bf6d23 Binary files /dev/null and b/public/images/avatars/female-04.jpg differ diff --git a/public/images/avatars/female-05.jpg b/public/images/avatars/female-05.jpg new file mode 100644 index 0000000..da7274e Binary files /dev/null and b/public/images/avatars/female-05.jpg differ diff --git a/public/images/avatars/female-06.jpg b/public/images/avatars/female-06.jpg new file mode 100644 index 0000000..a3b4fd7 Binary files /dev/null and b/public/images/avatars/female-06.jpg differ diff --git a/public/images/avatars/female-07.jpg b/public/images/avatars/female-07.jpg new file mode 100644 index 0000000..c151118 Binary files /dev/null and b/public/images/avatars/female-07.jpg differ diff --git a/public/images/avatars/female-08.jpg b/public/images/avatars/female-08.jpg new file mode 100644 index 0000000..319d9b9 Binary files /dev/null and b/public/images/avatars/female-08.jpg differ diff --git a/public/images/avatars/female-09.jpg b/public/images/avatars/female-09.jpg new file mode 100644 index 0000000..5a996f0 Binary files /dev/null and b/public/images/avatars/female-09.jpg differ diff --git a/public/images/avatars/female-10.jpg b/public/images/avatars/female-10.jpg new file mode 100644 index 0000000..c3a5fe3 Binary files /dev/null and b/public/images/avatars/female-10.jpg differ diff --git a/public/images/avatars/female-11.jpg b/public/images/avatars/female-11.jpg new file mode 100644 index 0000000..f14d50d Binary files /dev/null and b/public/images/avatars/female-11.jpg differ diff --git a/public/images/avatars/female-12.jpg b/public/images/avatars/female-12.jpg new file mode 100644 index 0000000..ab9dfa6 Binary files /dev/null and b/public/images/avatars/female-12.jpg differ diff --git a/public/images/avatars/female-13.jpg b/public/images/avatars/female-13.jpg new file mode 100644 index 0000000..950e157 Binary files /dev/null and b/public/images/avatars/female-13.jpg differ diff --git a/public/images/avatars/female-14.jpg b/public/images/avatars/female-14.jpg new file mode 100644 index 0000000..cdd6638 Binary files /dev/null and b/public/images/avatars/female-14.jpg differ diff --git a/public/images/avatars/female-15.jpg b/public/images/avatars/female-15.jpg new file mode 100644 index 0000000..e8a3efe Binary files /dev/null and b/public/images/avatars/female-15.jpg differ diff --git a/public/images/avatars/female-16.jpg b/public/images/avatars/female-16.jpg new file mode 100644 index 0000000..f697340 Binary files /dev/null and b/public/images/avatars/female-16.jpg differ diff --git a/public/images/avatars/female-17.jpg b/public/images/avatars/female-17.jpg new file mode 100644 index 0000000..5753fe4 Binary files /dev/null and b/public/images/avatars/female-17.jpg differ diff --git a/public/images/avatars/female-18.jpg b/public/images/avatars/female-18.jpg new file mode 100644 index 0000000..be2a702 Binary files /dev/null and b/public/images/avatars/female-18.jpg differ diff --git a/public/images/avatars/female-19.jpg b/public/images/avatars/female-19.jpg new file mode 100644 index 0000000..44ac8e0 Binary files /dev/null and b/public/images/avatars/female-19.jpg differ diff --git a/public/images/avatars/female-20.jpg b/public/images/avatars/female-20.jpg new file mode 100644 index 0000000..f998071 Binary files /dev/null and b/public/images/avatars/female-20.jpg differ diff --git a/public/images/avatars/male-01.jpg b/public/images/avatars/male-01.jpg new file mode 100644 index 0000000..dd0b05b Binary files /dev/null and b/public/images/avatars/male-01.jpg differ diff --git a/public/images/avatars/male-02.jpg b/public/images/avatars/male-02.jpg new file mode 100644 index 0000000..e938097 Binary files /dev/null and b/public/images/avatars/male-02.jpg differ diff --git a/public/images/avatars/male-03.jpg b/public/images/avatars/male-03.jpg new file mode 100644 index 0000000..48e4ffe Binary files /dev/null and b/public/images/avatars/male-03.jpg differ diff --git a/public/images/avatars/male-04.jpg b/public/images/avatars/male-04.jpg new file mode 100644 index 0000000..41f60b9 Binary files /dev/null and b/public/images/avatars/male-04.jpg differ diff --git a/public/images/avatars/male-05.jpg b/public/images/avatars/male-05.jpg new file mode 100644 index 0000000..5bb798e Binary files /dev/null and b/public/images/avatars/male-05.jpg differ diff --git a/public/images/avatars/male-06.jpg b/public/images/avatars/male-06.jpg new file mode 100644 index 0000000..fdaaf3c Binary files /dev/null and b/public/images/avatars/male-06.jpg differ diff --git a/public/images/avatars/male-07.jpg b/public/images/avatars/male-07.jpg new file mode 100644 index 0000000..55ea470 Binary files /dev/null and b/public/images/avatars/male-07.jpg differ diff --git a/public/images/avatars/male-08.jpg b/public/images/avatars/male-08.jpg new file mode 100644 index 0000000..7a4eaa0 Binary files /dev/null and b/public/images/avatars/male-08.jpg differ diff --git a/public/images/avatars/male-09.jpg b/public/images/avatars/male-09.jpg new file mode 100644 index 0000000..14e3fe3 Binary files /dev/null and b/public/images/avatars/male-09.jpg differ diff --git a/public/images/avatars/male-10.jpg b/public/images/avatars/male-10.jpg new file mode 100644 index 0000000..beeb6fb Binary files /dev/null and b/public/images/avatars/male-10.jpg differ diff --git a/public/images/avatars/male-11.jpg b/public/images/avatars/male-11.jpg new file mode 100644 index 0000000..7da7ce6 Binary files /dev/null and b/public/images/avatars/male-11.jpg differ diff --git a/public/images/avatars/male-12.jpg b/public/images/avatars/male-12.jpg new file mode 100644 index 0000000..a3e36ca Binary files /dev/null and b/public/images/avatars/male-12.jpg differ diff --git a/public/images/avatars/male-13.jpg b/public/images/avatars/male-13.jpg new file mode 100644 index 0000000..6af7b6b Binary files /dev/null and b/public/images/avatars/male-13.jpg differ diff --git a/public/images/avatars/male-14.jpg b/public/images/avatars/male-14.jpg new file mode 100644 index 0000000..d04d3d6 Binary files /dev/null and b/public/images/avatars/male-14.jpg differ diff --git a/public/images/avatars/male-15.jpg b/public/images/avatars/male-15.jpg new file mode 100644 index 0000000..fd8ecd2 Binary files /dev/null and b/public/images/avatars/male-15.jpg differ diff --git a/public/images/avatars/male-16.jpg b/public/images/avatars/male-16.jpg new file mode 100644 index 0000000..b2c063b Binary files /dev/null and b/public/images/avatars/male-16.jpg differ diff --git a/public/images/avatars/male-17.jpg b/public/images/avatars/male-17.jpg new file mode 100644 index 0000000..41ffcbc Binary files /dev/null and b/public/images/avatars/male-17.jpg differ diff --git a/public/images/avatars/male-18.jpg b/public/images/avatars/male-18.jpg new file mode 100644 index 0000000..60ee45f Binary files /dev/null and b/public/images/avatars/male-18.jpg differ diff --git a/public/images/avatars/male-19.jpg b/public/images/avatars/male-19.jpg new file mode 100644 index 0000000..7853dbd Binary files /dev/null and b/public/images/avatars/male-19.jpg differ diff --git a/public/images/avatars/male-20.jpg b/public/images/avatars/male-20.jpg new file mode 100644 index 0000000..c33807a Binary files /dev/null and b/public/images/avatars/male-20.jpg differ diff --git a/public/images/avatars/username.jpg b/public/images/avatars/username.jpg new file mode 100644 index 0000000..663b3d1 Binary files /dev/null and b/public/images/avatars/username.jpg differ diff --git a/public/images/cards/01-320x200.jpg b/public/images/cards/01-320x200.jpg new file mode 100644 index 0000000..adfeba7 Binary files /dev/null and b/public/images/cards/01-320x200.jpg differ diff --git a/public/images/cards/02-320x200.jpg b/public/images/cards/02-320x200.jpg new file mode 100644 index 0000000..7cc6ac9 Binary files /dev/null and b/public/images/cards/02-320x200.jpg differ diff --git a/public/images/cards/03-320x200.jpg b/public/images/cards/03-320x200.jpg new file mode 100644 index 0000000..766c7cd Binary files /dev/null and b/public/images/cards/03-320x200.jpg differ diff --git a/public/images/cards/04-320x200.jpg b/public/images/cards/04-320x200.jpg new file mode 100644 index 0000000..114ca88 Binary files /dev/null and b/public/images/cards/04-320x200.jpg differ diff --git a/public/images/cards/05-320x200.jpg b/public/images/cards/05-320x200.jpg new file mode 100644 index 0000000..64dca04 Binary files /dev/null and b/public/images/cards/05-320x200.jpg differ diff --git a/public/images/cards/06-320x200.jpg b/public/images/cards/06-320x200.jpg new file mode 100644 index 0000000..6eb295b Binary files /dev/null and b/public/images/cards/06-320x200.jpg differ diff --git a/public/images/cards/07-320x200.jpg b/public/images/cards/07-320x200.jpg new file mode 100644 index 0000000..101dfe1 Binary files /dev/null and b/public/images/cards/07-320x200.jpg differ diff --git a/public/images/cards/08-320x200.jpg b/public/images/cards/08-320x200.jpg new file mode 100644 index 0000000..049dbcf Binary files /dev/null and b/public/images/cards/08-320x200.jpg differ diff --git a/public/images/cards/09-320x200.jpg b/public/images/cards/09-320x200.jpg new file mode 100644 index 0000000..bbd8cf0 Binary files /dev/null and b/public/images/cards/09-320x200.jpg differ diff --git a/public/images/cards/10-320x200.jpg b/public/images/cards/10-320x200.jpg new file mode 100644 index 0000000..64cee47 Binary files /dev/null and b/public/images/cards/10-320x200.jpg differ diff --git a/public/images/cards/11-512x512.jpg b/public/images/cards/11-512x512.jpg new file mode 100644 index 0000000..5aa3a9c Binary files /dev/null and b/public/images/cards/11-512x512.jpg differ diff --git a/public/images/cards/12-512x512.jpg b/public/images/cards/12-512x512.jpg new file mode 100644 index 0000000..d8fe1d6 Binary files /dev/null and b/public/images/cards/12-512x512.jpg differ diff --git a/public/images/cards/13-160x160.jpg b/public/images/cards/13-160x160.jpg new file mode 100644 index 0000000..9343c8e Binary files /dev/null and b/public/images/cards/13-160x160.jpg differ diff --git a/public/images/cards/14-640x480.jpg b/public/images/cards/14-640x480.jpg new file mode 100644 index 0000000..d7d7769 Binary files /dev/null and b/public/images/cards/14-640x480.jpg differ diff --git a/public/images/cards/15-640x480.jpg b/public/images/cards/15-640x480.jpg new file mode 100644 index 0000000..a0dd48a Binary files /dev/null and b/public/images/cards/15-640x480.jpg differ diff --git a/public/images/cards/16-640x480.jpg b/public/images/cards/16-640x480.jpg new file mode 100644 index 0000000..ce3d1a3 Binary files /dev/null and b/public/images/cards/16-640x480.jpg differ diff --git a/public/images/cards/17-640x480.jpg b/public/images/cards/17-640x480.jpg new file mode 100644 index 0000000..896ac4c Binary files /dev/null and b/public/images/cards/17-640x480.jpg differ diff --git a/public/images/cards/18-640x480.jpg b/public/images/cards/18-640x480.jpg new file mode 100644 index 0000000..1ff403f Binary files /dev/null and b/public/images/cards/18-640x480.jpg differ diff --git a/public/images/cards/19-640x480.jpg b/public/images/cards/19-640x480.jpg new file mode 100644 index 0000000..242c5d0 Binary files /dev/null and b/public/images/cards/19-640x480.jpg differ diff --git a/public/images/cards/20-640x480.jpg b/public/images/cards/20-640x480.jpg new file mode 100644 index 0000000..47222dc Binary files /dev/null and b/public/images/cards/20-640x480.jpg differ diff --git a/public/images/cards/21-640x480.jpg b/public/images/cards/21-640x480.jpg new file mode 100644 index 0000000..dabf1c9 Binary files /dev/null and b/public/images/cards/21-640x480.jpg differ diff --git a/public/images/cards/22-640x480.jpg b/public/images/cards/22-640x480.jpg new file mode 100644 index 0000000..a81c474 Binary files /dev/null and b/public/images/cards/22-640x480.jpg differ diff --git a/public/images/cards/23-640x480.jpg b/public/images/cards/23-640x480.jpg new file mode 100644 index 0000000..f479adf Binary files /dev/null and b/public/images/cards/23-640x480.jpg differ diff --git a/public/images/cards/24-640x480.jpg b/public/images/cards/24-640x480.jpg new file mode 100644 index 0000000..15b6b17 Binary files /dev/null and b/public/images/cards/24-640x480.jpg differ diff --git a/public/images/cards/25-640x480.jpg b/public/images/cards/25-640x480.jpg new file mode 100644 index 0000000..07f25c1 Binary files /dev/null and b/public/images/cards/25-640x480.jpg differ diff --git a/public/images/cards/26-640x480.jpg b/public/images/cards/26-640x480.jpg new file mode 100644 index 0000000..fa1faf0 Binary files /dev/null and b/public/images/cards/26-640x480.jpg differ diff --git a/public/images/cards/27-640x480.jpg b/public/images/cards/27-640x480.jpg new file mode 100644 index 0000000..f4f5e9c Binary files /dev/null and b/public/images/cards/27-640x480.jpg differ diff --git a/public/images/cards/28-640x480.jpg b/public/images/cards/28-640x480.jpg new file mode 100644 index 0000000..8a9d5ea Binary files /dev/null and b/public/images/cards/28-640x480.jpg differ diff --git a/public/images/cards/29-640x480.jpg b/public/images/cards/29-640x480.jpg new file mode 100644 index 0000000..bb491a0 Binary files /dev/null and b/public/images/cards/29-640x480.jpg differ diff --git a/public/images/cards/30-640x480.jpg b/public/images/cards/30-640x480.jpg new file mode 100644 index 0000000..525dd79 Binary files /dev/null and b/public/images/cards/30-640x480.jpg differ diff --git a/public/images/cards/31-640x480.jpg b/public/images/cards/31-640x480.jpg new file mode 100644 index 0000000..b09020d Binary files /dev/null and b/public/images/cards/31-640x480.jpg differ diff --git a/public/images/cards/32-640x480.jpg b/public/images/cards/32-640x480.jpg new file mode 100644 index 0000000..3bb94c6 Binary files /dev/null and b/public/images/cards/32-640x480.jpg differ diff --git a/public/images/cards/33-640x480.jpg b/public/images/cards/33-640x480.jpg new file mode 100644 index 0000000..36cde18 Binary files /dev/null and b/public/images/cards/33-640x480.jpg differ diff --git a/public/images/cards/34-640x480.jpg b/public/images/cards/34-640x480.jpg new file mode 100644 index 0000000..34f07fa Binary files /dev/null and b/public/images/cards/34-640x480.jpg differ diff --git a/public/images/cards/35-640x480.jpg b/public/images/cards/35-640x480.jpg new file mode 100644 index 0000000..41f4a61 Binary files /dev/null and b/public/images/cards/35-640x480.jpg differ diff --git a/public/images/cards/36-640x480.jpg b/public/images/cards/36-640x480.jpg new file mode 100644 index 0000000..0cdc47b Binary files /dev/null and b/public/images/cards/36-640x480.jpg differ diff --git a/public/images/cards/avatar-400x400.jpg b/public/images/cards/avatar-400x400.jpg new file mode 100644 index 0000000..7898e4e Binary files /dev/null and b/public/images/cards/avatar-400x400.jpg differ diff --git a/public/images/cards/coffee-shop-01-320x200.jpg b/public/images/cards/coffee-shop-01-320x200.jpg new file mode 100644 index 0000000..e1fe307 Binary files /dev/null and b/public/images/cards/coffee-shop-01-320x200.jpg differ diff --git a/public/images/cards/coffee-shop-02-512x512.jpg b/public/images/cards/coffee-shop-02-512x512.jpg new file mode 100644 index 0000000..2ac8a09 Binary files /dev/null and b/public/images/cards/coffee-shop-02-512x512.jpg differ diff --git a/public/images/cards/coffee-shop-03-320x320.jpg b/public/images/cards/coffee-shop-03-320x320.jpg new file mode 100644 index 0000000..28a9128 Binary files /dev/null and b/public/images/cards/coffee-shop-03-320x320.jpg differ diff --git a/public/images/cards/mansion-01-320x200.jpg b/public/images/cards/mansion-01-320x200.jpg new file mode 100644 index 0000000..869f40f Binary files /dev/null and b/public/images/cards/mansion-01-320x200.jpg differ diff --git a/public/images/cards/product-01-224x256.jpg b/public/images/cards/product-01-224x256.jpg new file mode 100644 index 0000000..b4144e8 Binary files /dev/null and b/public/images/cards/product-01-224x256.jpg differ diff --git a/public/images/cards/sneakers-01-320x200.jpg b/public/images/cards/sneakers-01-320x200.jpg new file mode 100644 index 0000000..64bc157 Binary files /dev/null and b/public/images/cards/sneakers-01-320x200.jpg differ diff --git a/public/images/cards/sneakers-02-448x560.jpg b/public/images/cards/sneakers-02-448x560.jpg new file mode 100644 index 0000000..08b2040 Binary files /dev/null and b/public/images/cards/sneakers-02-448x560.jpg differ diff --git a/public/images/cards/sneakers-03-448x560.jpg b/public/images/cards/sneakers-03-448x560.jpg new file mode 100644 index 0000000..15f9fdc Binary files /dev/null and b/public/images/cards/sneakers-03-448x560.jpg differ diff --git a/public/images/flags/TR.svg b/public/images/flags/TR.svg new file mode 100644 index 0000000..f092549 --- /dev/null +++ b/public/images/flags/TR.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/images/flags/US.svg b/public/images/flags/US.svg new file mode 100644 index 0000000..847c732 --- /dev/null +++ b/public/images/flags/US.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/images/flags/where-to-find-other-flags.txt b/public/images/flags/where-to-find-other-flags.txt new file mode 100644 index 0000000..dcee53a --- /dev/null +++ b/public/images/flags/where-to-find-other-flags.txt @@ -0,0 +1,5 @@ +### Main repository of the flags ### +https://github.com/Yummygum/flagpack-core + +### We used the medium (m) detailed SVGs which are located here ### +https://github.com/Yummygum/flagpack-core/tree/main/svg/m diff --git a/public/images/logo/logo-text-on-dark.svg b/public/images/logo/logo-text-on-dark.svg new file mode 100644 index 0000000..06c642c --- /dev/null +++ b/public/images/logo/logo-text-on-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/logo/logo-text.svg b/public/images/logo/logo-text.svg new file mode 100644 index 0000000..06c642c --- /dev/null +++ b/public/images/logo/logo-text.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/images/logo/logo.svg b/public/images/logo/logo.svg new file mode 100644 index 0000000..7c40eaa --- /dev/null +++ b/public/images/logo/logo.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/images/pages/help-center/image-1.jpg b/public/images/pages/help-center/image-1.jpg new file mode 100644 index 0000000..8e734f4 Binary files /dev/null and b/public/images/pages/help-center/image-1.jpg differ diff --git a/public/images/pages/profile/cover.jpg b/public/images/pages/profile/cover.jpg new file mode 100644 index 0000000..18d4538 Binary files /dev/null and b/public/images/pages/profile/cover.jpg differ diff --git a/public/images/ui/angular-material/scenes/autocomplete.scene.png b/public/images/ui/angular-material/scenes/autocomplete.scene.png new file mode 100644 index 0000000..de997b1 Binary files /dev/null and b/public/images/ui/angular-material/scenes/autocomplete.scene.png differ diff --git a/public/images/ui/angular-material/scenes/badge.scene.png b/public/images/ui/angular-material/scenes/badge.scene.png new file mode 100644 index 0000000..e9a79db Binary files /dev/null and b/public/images/ui/angular-material/scenes/badge.scene.png differ diff --git a/public/images/ui/angular-material/scenes/bottom-sheet.scene.png b/public/images/ui/angular-material/scenes/bottom-sheet.scene.png new file mode 100644 index 0000000..f23e864 Binary files /dev/null and b/public/images/ui/angular-material/scenes/bottom-sheet.scene.png differ diff --git a/public/images/ui/angular-material/scenes/button-toggle.scene.png b/public/images/ui/angular-material/scenes/button-toggle.scene.png new file mode 100644 index 0000000..5a227fd Binary files /dev/null and b/public/images/ui/angular-material/scenes/button-toggle.scene.png differ diff --git a/public/images/ui/angular-material/scenes/button.scene.png b/public/images/ui/angular-material/scenes/button.scene.png new file mode 100644 index 0000000..74448b3 Binary files /dev/null and b/public/images/ui/angular-material/scenes/button.scene.png differ diff --git a/public/images/ui/angular-material/scenes/card.scene.png b/public/images/ui/angular-material/scenes/card.scene.png new file mode 100644 index 0000000..4f6cac9 Binary files /dev/null and b/public/images/ui/angular-material/scenes/card.scene.png differ diff --git a/public/images/ui/angular-material/scenes/checkbox.scene.png b/public/images/ui/angular-material/scenes/checkbox.scene.png new file mode 100644 index 0000000..7b0327a Binary files /dev/null and b/public/images/ui/angular-material/scenes/checkbox.scene.png differ diff --git a/public/images/ui/angular-material/scenes/chips.scene.png b/public/images/ui/angular-material/scenes/chips.scene.png new file mode 100644 index 0000000..40f6085 Binary files /dev/null and b/public/images/ui/angular-material/scenes/chips.scene.png differ diff --git a/public/images/ui/angular-material/scenes/core.scene.png b/public/images/ui/angular-material/scenes/core.scene.png new file mode 100644 index 0000000..9f70df7 Binary files /dev/null and b/public/images/ui/angular-material/scenes/core.scene.png differ diff --git a/public/images/ui/angular-material/scenes/datepicker.scene.png b/public/images/ui/angular-material/scenes/datepicker.scene.png new file mode 100644 index 0000000..8bfbd87 Binary files /dev/null and b/public/images/ui/angular-material/scenes/datepicker.scene.png differ diff --git a/public/images/ui/angular-material/scenes/dialog.scene.png b/public/images/ui/angular-material/scenes/dialog.scene.png new file mode 100644 index 0000000..ff3c352 Binary files /dev/null and b/public/images/ui/angular-material/scenes/dialog.scene.png differ diff --git a/public/images/ui/angular-material/scenes/divider.scene.png b/public/images/ui/angular-material/scenes/divider.scene.png new file mode 100644 index 0000000..ac7749f Binary files /dev/null and b/public/images/ui/angular-material/scenes/divider.scene.png differ diff --git a/public/images/ui/angular-material/scenes/expansion.scene.png b/public/images/ui/angular-material/scenes/expansion.scene.png new file mode 100644 index 0000000..c95ce30 Binary files /dev/null and b/public/images/ui/angular-material/scenes/expansion.scene.png differ diff --git a/public/images/ui/angular-material/scenes/form-field.scene.png b/public/images/ui/angular-material/scenes/form-field.scene.png new file mode 100644 index 0000000..868454d Binary files /dev/null and b/public/images/ui/angular-material/scenes/form-field.scene.png differ diff --git a/public/images/ui/angular-material/scenes/grid-list.scene.png b/public/images/ui/angular-material/scenes/grid-list.scene.png new file mode 100644 index 0000000..236fe74 Binary files /dev/null and b/public/images/ui/angular-material/scenes/grid-list.scene.png differ diff --git a/public/images/ui/angular-material/scenes/icon.scene.png b/public/images/ui/angular-material/scenes/icon.scene.png new file mode 100644 index 0000000..0e94810 Binary files /dev/null and b/public/images/ui/angular-material/scenes/icon.scene.png differ diff --git a/public/images/ui/angular-material/scenes/input.scene.png b/public/images/ui/angular-material/scenes/input.scene.png new file mode 100644 index 0000000..653478f Binary files /dev/null and b/public/images/ui/angular-material/scenes/input.scene.png differ diff --git a/public/images/ui/angular-material/scenes/list.scene.png b/public/images/ui/angular-material/scenes/list.scene.png new file mode 100644 index 0000000..575c4fc Binary files /dev/null and b/public/images/ui/angular-material/scenes/list.scene.png differ diff --git a/public/images/ui/angular-material/scenes/menu.scene.png b/public/images/ui/angular-material/scenes/menu.scene.png new file mode 100644 index 0000000..e9ec270 Binary files /dev/null and b/public/images/ui/angular-material/scenes/menu.scene.png differ diff --git a/public/images/ui/angular-material/scenes/paginator.scene.png b/public/images/ui/angular-material/scenes/paginator.scene.png new file mode 100644 index 0000000..8c39357 Binary files /dev/null and b/public/images/ui/angular-material/scenes/paginator.scene.png differ diff --git a/public/images/ui/angular-material/scenes/progress-bar.scene.png b/public/images/ui/angular-material/scenes/progress-bar.scene.png new file mode 100644 index 0000000..296e9dc Binary files /dev/null and b/public/images/ui/angular-material/scenes/progress-bar.scene.png differ diff --git a/public/images/ui/angular-material/scenes/progress-spinner.scene.png b/public/images/ui/angular-material/scenes/progress-spinner.scene.png new file mode 100644 index 0000000..869917c Binary files /dev/null and b/public/images/ui/angular-material/scenes/progress-spinner.scene.png differ diff --git a/public/images/ui/angular-material/scenes/radio.scene.png b/public/images/ui/angular-material/scenes/radio.scene.png new file mode 100644 index 0000000..ecac3b1 Binary files /dev/null and b/public/images/ui/angular-material/scenes/radio.scene.png differ diff --git a/public/images/ui/angular-material/scenes/ripple.scene.png b/public/images/ui/angular-material/scenes/ripple.scene.png new file mode 100644 index 0000000..9d9c93d Binary files /dev/null and b/public/images/ui/angular-material/scenes/ripple.scene.png differ diff --git a/public/images/ui/angular-material/scenes/select.scene.png b/public/images/ui/angular-material/scenes/select.scene.png new file mode 100644 index 0000000..90b44cc Binary files /dev/null and b/public/images/ui/angular-material/scenes/select.scene.png differ diff --git a/public/images/ui/angular-material/scenes/sidenav.scene.png b/public/images/ui/angular-material/scenes/sidenav.scene.png new file mode 100644 index 0000000..18ceeac Binary files /dev/null and b/public/images/ui/angular-material/scenes/sidenav.scene.png differ diff --git a/public/images/ui/angular-material/scenes/slide-toggle.scene.png b/public/images/ui/angular-material/scenes/slide-toggle.scene.png new file mode 100644 index 0000000..15ea831 Binary files /dev/null and b/public/images/ui/angular-material/scenes/slide-toggle.scene.png differ diff --git a/public/images/ui/angular-material/scenes/slider.scene.png b/public/images/ui/angular-material/scenes/slider.scene.png new file mode 100644 index 0000000..8bae38c Binary files /dev/null and b/public/images/ui/angular-material/scenes/slider.scene.png differ diff --git a/public/images/ui/angular-material/scenes/snack-bar.scene.png b/public/images/ui/angular-material/scenes/snack-bar.scene.png new file mode 100644 index 0000000..d327b5d Binary files /dev/null and b/public/images/ui/angular-material/scenes/snack-bar.scene.png differ diff --git a/public/images/ui/angular-material/scenes/sort.scene.png b/public/images/ui/angular-material/scenes/sort.scene.png new file mode 100644 index 0000000..8055fe0 Binary files /dev/null and b/public/images/ui/angular-material/scenes/sort.scene.png differ diff --git a/public/images/ui/angular-material/scenes/stepper.scene.png b/public/images/ui/angular-material/scenes/stepper.scene.png new file mode 100644 index 0000000..031aade Binary files /dev/null and b/public/images/ui/angular-material/scenes/stepper.scene.png differ diff --git a/public/images/ui/angular-material/scenes/table.scene.png b/public/images/ui/angular-material/scenes/table.scene.png new file mode 100644 index 0000000..749a16a Binary files /dev/null and b/public/images/ui/angular-material/scenes/table.scene.png differ diff --git a/public/images/ui/angular-material/scenes/tabs.scene.png b/public/images/ui/angular-material/scenes/tabs.scene.png new file mode 100644 index 0000000..22d9d20 Binary files /dev/null and b/public/images/ui/angular-material/scenes/tabs.scene.png differ diff --git a/public/images/ui/angular-material/scenes/toolbar.scene.png b/public/images/ui/angular-material/scenes/toolbar.scene.png new file mode 100644 index 0000000..1cf4268 Binary files /dev/null and b/public/images/ui/angular-material/scenes/toolbar.scene.png differ diff --git a/public/images/ui/angular-material/scenes/tooltip.scene.png b/public/images/ui/angular-material/scenes/tooltip.scene.png new file mode 100644 index 0000000..6d03280 Binary files /dev/null and b/public/images/ui/angular-material/scenes/tooltip.scene.png differ diff --git a/public/images/ui/angular-material/scenes/tree.scene.png b/public/images/ui/angular-material/scenes/tree.scene.png new file mode 100644 index 0000000..d8aec40 Binary files /dev/null and b/public/images/ui/angular-material/scenes/tree.scene.png differ diff --git a/public/styles/splash-screen.css b/public/styles/splash-screen.css new file mode 100644 index 0000000..f0a0318 --- /dev/null +++ b/public/styles/splash-screen.css @@ -0,0 +1,80 @@ +body angor-splash-screen { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #022229; + color: #F9FAFB; + z-index: 999999; + pointer-events: none; + opacity: 1; + visibility: visible; + transition: opacity 400ms cubic-bezier(0.4, 0, 0.2, 1); +} + +body angor-splash-screen img { + width: 120px; + max-width: 120px; +} + +body angor-splash-screen .spinner { + display: flex; + align-items: center; + justify-content: space-between; + margin-top: 40px; + width: 56px; +} + +body angor-splash-screen .spinner > div { + width: 12px; + height: 12px; + background-color: #083b46; + border-radius: 100%; + display: inline-block; + -webkit-animation: angor-bouncedelay 1s infinite ease-in-out both; + animation: angor-bouncedelay 1s infinite ease-in-out both; +} + +body angor-splash-screen .spinner .bounce1 { + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; +} + +body angor-splash-screen .spinner .bounce2 { + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; +} + +@-webkit-keyframes angor-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0) + } + 40% { + -webkit-transform: scale(1.0) + } +} + +@keyframes angor-bouncedelay { + 0%, 80%, 100% { + -webkit-transform: scale(0); + transform: scale(0); + } + 40% { + -webkit-transform: scale(1.0); + transform: scale(1.0); + } +} + +body:not(.angor-splash-screen-hidden) { + overflow: hidden; +} + +body.angor-splash-screen-hidden angor-splash-screen { + visibility: hidden; + opacity: 0; +} diff --git a/src/@angor/angor.provider.ts b/src/@angor/angor.provider.ts new file mode 100644 index 0000000..57a1fb9 --- /dev/null +++ b/src/@angor/angor.provider.ts @@ -0,0 +1,121 @@ +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { + APP_INITIALIZER, + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + Provider, + importProvidersFrom, + inject, +} from '@angular/core'; +import { MATERIAL_SANITY_CHECKS } from '@angular/material/core'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field'; +import { + ANGOR_MOCK_API_DEFAULT_DELAY, + mockApiInterceptor, +} from '@angor/lib/mock-api'; +import { AngorConfig } from '@angor/services/config'; +import { ANGOR_CONFIG } from '@angor/services/config/config.constants'; +import { AngorConfirmationService } from '@angor/services/confirmation'; +import { + AngorLoadingService, + angorLoadingInterceptor, +} from '@angor/services/loading'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { AngorPlatformService } from '@angor/services/platform'; +import { AngorSplashScreenService } from '@angor/services/splash-screen'; +import { AngorUtilsService } from '@angor/services/utils'; + +export type AngorProviderConfig = { + mockApi?: { + delay?: number; + services?: any[]; + }; + angor?: AngorConfig; +}; + +/** + * Angor provider + */ +export const provideAngor = ( + config: AngorProviderConfig +): Array => { + // Base providers + const providers: Array = [ + { + // Disable 'theme' sanity check + provide: MATERIAL_SANITY_CHECKS, + useValue: { + doctype: true, + theme: false, + version: true, + }, + }, + { + // Use the 'fill' appearance on Angular Material form fields by default + provide: MAT_FORM_FIELD_DEFAULT_OPTIONS, + useValue: { + appearance: 'fill', + }, + }, + { + provide: ANGOR_MOCK_API_DEFAULT_DELAY, + useValue: config?.mockApi?.delay ?? 0, + }, + { + provide: ANGOR_CONFIG, + useValue: config?.angor ?? {}, + }, + + importProvidersFrom(MatDialogModule), + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorConfirmationService), + multi: true, + }, + + provideHttpClient(withInterceptors([angorLoadingInterceptor])), + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorLoadingService), + multi: true, + }, + + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorMediaWatcherService), + multi: true, + }, + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorPlatformService), + multi: true, + }, + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorSplashScreenService), + multi: true, + }, + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AngorUtilsService), + multi: true, + }, + ]; + + // Mock Api services + if (config?.mockApi?.services) { + providers.push( + provideHttpClient(withInterceptors([mockApiInterceptor])), + { + provide: APP_INITIALIZER, + deps: [...config.mockApi.services], + useFactory: () => (): any => null, + multi: true, + } + ); + } + + // Return the providers + return providers; +}; diff --git a/src/@angor/animations/defaults.ts b/src/@angor/animations/defaults.ts new file mode 100644 index 0000000..f08a551 --- /dev/null +++ b/src/@angor/animations/defaults.ts @@ -0,0 +1,18 @@ +/** + * Defines animation curves for Angor. + */ +export class AngorAnimationCurves { + static standard = 'cubic-bezier(0.4, 0.0, 0.2, 1)'; // Standard animation curve + static deceleration = 'cubic-bezier(0.0, 0.0, 0.2, 1)'; // Deceleration curve + static acceleration = 'cubic-bezier(0.4, 0.0, 1, 1)'; // Acceleration curve + static sharp = 'cubic-bezier(0.4, 0.0, 0.6, 1)'; // Sharp curve +} + +/** + * Defines animation durations for Angor. + */ +export class AngorAnimationDurations { + static complex = '375ms'; // Duration for complex animations + static entering = '225ms'; // Duration for entering animations + static exiting = '195ms'; // Duration for exiting animations +} diff --git a/src/@angor/animations/expand-collapse.ts b/src/@angor/animations/expand-collapse.ts new file mode 100644 index 0000000..15bf1eb --- /dev/null +++ b/src/@angor/animations/expand-collapse.ts @@ -0,0 +1,39 @@ +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; + +/** + * Animation trigger for expand/collapse transitions + */ +const expandCollapse = trigger('expandCollapse', [ + // Collapsed state with height 0 + state( + 'void, collapsed', + style({ + height: '0', + }) + ), + + // Expanded state with natural height + state('*, expanded', style('*')), + + // No transition when state is false + transition('void <=> false, collapsed <=> false, expanded <=> false', []), + + // Transition between collapsed and expanded states + transition('void <=> *, collapsed <=> expanded', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +export { expandCollapse }; diff --git a/src/@angor/animations/fade.ts b/src/@angor/animations/fade.ts new file mode 100644 index 0000000..8813db4 --- /dev/null +++ b/src/@angor/animations/fade.ts @@ -0,0 +1,178 @@ +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; + +/** + * Fade in animation trigger + */ +const fadeIn = trigger('fadeIn', [ + state('void', style({ opacity: 0 })), + state('*', style({ opacity: 1 })), + + // No transition if state is false + transition('void => false', []), + + // Transition for fade-in effect + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Fade in from top animation trigger + */ +const fadeInTop = trigger('fadeInTop', [ + state('void', style({ opacity: 0, transform: 'translate3d(0, -100%, 0)' })), + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Fade in from bottom animation trigger + */ +const fadeInBottom = trigger('fadeInBottom', [ + state('void', style({ opacity: 0, transform: 'translate3d(0, 100%, 0)' })), + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Fade in from left animation trigger + */ +const fadeInLeft = trigger('fadeInLeft', [ + state('void', style({ opacity: 0, transform: 'translate3d(-100%, 0, 0)' })), + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Fade in from right animation trigger + */ +const fadeInRight = trigger('fadeInRight', [ + state('void', style({ opacity: 0, transform: 'translate3d(100%, 0, 0)' })), + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Fade out animation trigger + */ +const fadeOut = trigger('fadeOut', [ + state('*', style({ opacity: 1 })), + state('void', style({ opacity: 0 })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Fade out to top animation trigger + */ +const fadeOutTop = trigger('fadeOutTop', [ + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + state('void', style({ opacity: 0, transform: 'translate3d(0, -100%, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Fade out to bottom animation trigger + */ +const fadeOutBottom = trigger('fadeOutBottom', [ + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + state('void', style({ opacity: 0, transform: 'translate3d(0, 100%, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Fade out to left animation trigger + */ +const fadeOutLeft = trigger('fadeOutLeft', [ + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + state('void', style({ opacity: 0, transform: 'translate3d(-100%, 0, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Fade out to right animation trigger + */ +const fadeOutRight = trigger('fadeOutRight', [ + state('*', style({ opacity: 1, transform: 'translate3d(0, 0, 0)' })), + state('void', style({ opacity: 0, transform: 'translate3d(100%, 0, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +// Export all triggers +export { + fadeIn, + fadeInBottom, + fadeInLeft, + fadeInRight, + fadeInTop, + fadeOut, + fadeOutBottom, + fadeOutLeft, + fadeOutRight, + fadeOutTop, +}; diff --git a/src/@angor/animations/index.ts b/src/@angor/animations/index.ts new file mode 100644 index 0000000..4390f52 --- /dev/null +++ b/src/@angor/animations/index.ts @@ -0,0 +1 @@ +export * from '@angor/animations/public-api'; diff --git a/src/@angor/animations/public-api.ts b/src/@angor/animations/public-api.ts new file mode 100644 index 0000000..ec88c30 --- /dev/null +++ b/src/@angor/animations/public-api.ts @@ -0,0 +1,53 @@ +import { expandCollapse } from '@angor/animations/expand-collapse'; +import { + fadeIn, + fadeInBottom, + fadeInLeft, + fadeInRight, + fadeInTop, + fadeOut, + fadeOutBottom, + fadeOutLeft, + fadeOutRight, + fadeOutTop, +} from '@angor/animations/fade'; +import { shake } from '@angor/animations/shake'; +import { + slideInBottom, + slideInLeft, + slideInRight, + slideInTop, + slideOutBottom, + slideOutLeft, + slideOutRight, + slideOutTop, +} from '@angor/animations/slide'; +import { zoomIn, zoomOut } from '@angor/animations/zoom'; + +/** + * Array of all Angor animations + */ +export const angorAnimations = [ + expandCollapse, + fadeIn, + fadeInTop, + fadeInBottom, + fadeInLeft, + fadeInRight, + fadeOut, + fadeOutTop, + fadeOutBottom, + fadeOutLeft, + fadeOutRight, + shake, + slideInTop, + slideInBottom, + slideInLeft, + slideInRight, + slideOutTop, + slideOutBottom, + slideOutLeft, + slideOutRight, + zoomIn, + zoomOut, +]; diff --git a/src/@angor/animations/shake.ts b/src/@angor/animations/shake.ts new file mode 100644 index 0000000..a0c2d88 --- /dev/null +++ b/src/@angor/animations/shake.ts @@ -0,0 +1,45 @@ +import { + animate, + keyframes, + style, + transition, + trigger, +} from '@angular/animations'; + +/** + * Shake animation trigger + */ +const shake = trigger('shake', [ + // Prevent the transition if the state is false + transition('void => false', []), + + // Shake animation transition + transition( + 'void => *, * => true', + [ + animate( + '{{timings}}', + keyframes([ + style({ transform: 'translate3d(0, 0, 0)', offset: 0 }), + style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.1 }), + style({ transform: 'translate3d(10px, 0, 0)', offset: 0.2 }), + style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.3 }), + style({ transform: 'translate3d(10px, 0, 0)', offset: 0.4 }), + style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.5 }), + style({ transform: 'translate3d(10px, 0, 0)', offset: 0.6 }), + style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.7 }), + style({ transform: 'translate3d(10px, 0, 0)', offset: 0.8 }), + style({ transform: 'translate3d(-10px, 0, 0)', offset: 0.9 }), + style({ transform: 'translate3d(0, 0, 0)', offset: 1 }), + ]) + ), + ], + { + params: { + timings: '0.8s cubic-bezier(0.455, 0.03, 0.515, 0.955)', // Timing for the shake animation + }, + } + ), +]); + +export { shake }; diff --git a/src/@angor/animations/slide.ts b/src/@angor/animations/slide.ts new file mode 100644 index 0000000..57b6222 --- /dev/null +++ b/src/@angor/animations/slide.ts @@ -0,0 +1,146 @@ +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; + +/** + * Slide in from top animation trigger + */ +const slideInTop = trigger('slideInTop', [ + state('void', style({ transform: 'translate3d(0, -100%, 0)' })), + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + + // No transition if state is false + transition('void => false', []), + + // Slide in transition + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Slide in from bottom animation trigger + */ +const slideInBottom = trigger('slideInBottom', [ + state('void', style({ transform: 'translate3d(0, 100%, 0)' })), + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Slide in from left animation trigger + */ +const slideInLeft = trigger('slideInLeft', [ + state('void', style({ transform: 'translate3d(-100%, 0, 0)' })), + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Slide in from right animation trigger + */ +const slideInRight = trigger('slideInRight', [ + state('void', style({ transform: 'translate3d(100%, 0, 0)' })), + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + + transition('void => false', []), + transition('void => *', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + }, + }), +]); + +/** + * Slide out to top animation trigger + */ +const slideOutTop = trigger('slideOutTop', [ + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + state('void', style({ transform: 'translate3d(0, -100%, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Slide out to bottom animation trigger + */ +const slideOutBottom = trigger('slideOutBottom', [ + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + state('void', style({ transform: 'translate3d(0, 100%, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Slide out to left animation trigger + */ +const slideOutLeft = trigger('slideOutLeft', [ + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + state('void', style({ transform: 'translate3d(-100%, 0, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +/** + * Slide out to right animation trigger + */ +const slideOutRight = trigger('slideOutRight', [ + state('*', style({ transform: 'translate3d(0, 0, 0)' })), + state('void', style({ transform: 'translate3d(100%, 0, 0)' })), + + transition('false => void', []), + transition('* => void', animate('{{timings}}'), { + params: { + timings: `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}`, + }, + }), +]); + +// Export all slide animations +export { + slideInBottom, + slideInLeft, + slideInRight, + slideInTop, + slideOutBottom, + slideOutLeft, + slideOutRight, + slideOutTop, +}; diff --git a/src/@angor/animations/zoom.ts b/src/@angor/animations/zoom.ts new file mode 100644 index 0000000..1775f97 --- /dev/null +++ b/src/@angor/animations/zoom.ts @@ -0,0 +1,69 @@ +import { + animate, + state, + style, + transition, + trigger, +} from '@angular/animations'; +import { + AngorAnimationCurves, + AngorAnimationDurations, +} from '@angor/animations/defaults'; + +/** + * Creates a reusable animation trigger with configurable parameters. + * @param name The name of the animation trigger. + * @param initialState The initial state style. + * @param finalState The final state style. + * @param timings Animation timings and curves. + * @param entering Default timings for entering state. + * @param exiting Default timings for exiting state. + */ +const createAnimationTrigger = ( + name: string, + initialState: Record, + finalState: Record, + timings: string, + entering: string = `${AngorAnimationDurations.entering} ${AngorAnimationCurves.deceleration}`, + exiting: string = `${AngorAnimationDurations.exiting} ${AngorAnimationCurves.acceleration}` +) => { + return trigger(name, [ + state('void', style(initialState)), + state('*', style(finalState)), + + // No transition if state is false + transition('void => false, * => false', []), + + // Transition for entering state + transition('void => *', animate(timings || entering), { + params: { timings: entering }, + }), + + // Transition for exiting state + transition('* => void', animate(timings || exiting), { + params: { timings: exiting }, + }), + ]); +}; + +/** + * Zoom in animation trigger using the reusable function + */ +const zoomIn = createAnimationTrigger( + 'zoomIn', + { opacity: 0, transform: 'scale(0.5)' }, + { opacity: 1, transform: 'scale(1)' }, + '' +); + +/** + * Zoom out animation trigger using the reusable function + */ +const zoomOut = createAnimationTrigger( + 'zoomOut', + { opacity: 1, transform: 'scale(1)' }, + { opacity: 0, transform: 'scale(0.5)' }, + '' +); + +export { zoomIn, zoomOut }; diff --git a/src/@angor/components/alert/alert.component.html b/src/@angor/components/alert/alert.component.html new file mode 100644 index 0000000..d3c3240 --- /dev/null +++ b/src/@angor/components/alert/alert.component.html @@ -0,0 +1,93 @@ +@if (!dismissible || (dismissible && !dismissed)) { +
+ + @if (appearance === 'border') { +
+ } + + + @if (showIcon) { +
+ +
+ +
+ + +
+ @if (type === 'primary') { + + } + + @if (type === 'accent') { + + } + + @if (type === 'warn') { + + } + + @if (type === 'basic') { + + } + + @if (type === 'info') { + + } + + @if (type === 'success') { + + } + + @if (type === 'warning') { + + } + + @if (type === 'error') { + + } +
+
+ } + + +
+
+ +
+ +
+ +
+
+ + + +
+} diff --git a/src/@angor/components/alert/alert.component.scss b/src/@angor/components/alert/alert.component.scss new file mode 100644 index 0000000..620d611 --- /dev/null +++ b/src/@angor/components/alert/alert.component.scss @@ -0,0 +1,1290 @@ +angor-alert { + display: block; + + /* Common */ + .angor-alert-container { + position: relative; + display: flex; + padding: 16px; + font-size: 14px; + line-height: 1; + + /* All icons */ + .mat-icon { + color: currentColor !important; + } + + /* Icon */ + .angor-alert-icon { + display: flex; + align-items: flex-start; + + .angor-alert-custom-icon, + .angor-alert-default-icon { + display: none; + align-items: center; + justify-content: center; + border-radius: 50%; + + &:not(:empty) { + display: flex; + margin-right: 12px; + } + } + + .angor-alert-default-icon { + .mat-icon { + @apply icon-size-5; + } + } + + .angor-alert-custom-icon { + display: none; + + &:not(:empty) { + display: flex; + + + .angor-alert-default-icon { + display: none; + } + } + } + } + + /* Content */ + .angor-alert-content { + display: flex; + flex-direction: column; + justify-content: center; + line-height: 1; + + /* Title */ + .angor-alert-title { + display: none; + font-weight: 600; + line-height: 20px; + + &:not(:empty) { + display: block; + + /* Alert that comes after the title */ + + .angor-alert-message { + &:not(:empty) { + margin-top: 4px; + } + } + } + } + + /* Alert */ + .angor-alert-message { + display: none; + line-height: 20px; + + &:not(:empty) { + display: block; + } + } + } + + /* Dismiss button */ + .angor-alert-dismiss-button { + position: absolute; + top: 10px; + right: 10px; + width: 32px !important; + min-width: 32px !important; + height: 32px !important; + min-height: 32px !important; + line-height: 32px !important; + + .mat-icon { + @apply icon-size-4; + } + } + } + + /* Dismissible */ + &.angor-alert-dismissible { + .angor-alert-container { + .angor-alert-content { + margin-right: 32px; + } + } + } + + &:not(.angor-alert-dismissible) { + .angor-alert-container { + .angor-alert-dismiss-button { + display: none !important; + } + } + } + + /* Border */ + &.angor-alert-appearance-border { + .angor-alert-container { + position: relative; + overflow: hidden; + border-radius: 6px; + @apply bg-card shadow-md; + + .angor-alert-border { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 4px; + } + + .angor-alert-message { + @apply text-gray-600; + } + } + + /* Primary */ + &.angor-alert-type-primary { + .angor-alert-container { + .angor-alert-border { + @apply bg-primary; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-primary; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-primary-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-primary-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Accent */ + &.angor-alert-type-accent { + .angor-alert-container { + .angor-alert-border { + @apply bg-accent; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-accent; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-accent-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-accent-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Warn */ + &.angor-alert-type-warn { + .angor-alert-container { + .angor-alert-border { + @apply bg-warn; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-warn; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-warn-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-warn-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Basic */ + &.angor-alert-type-basic { + .angor-alert-container { + .angor-alert-border { + @apply bg-gray-600; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-gray-600; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-gray-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-gray-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Info */ + &.angor-alert-type-info { + .angor-alert-container { + .angor-alert-border { + @apply bg-blue-600; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-blue-700; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-blue-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-blue-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Success */ + &.angor-alert-type-success { + .angor-alert-container { + .angor-alert-border { + @apply bg-green-500; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-green-500; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-green-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-green-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Warning */ + &.angor-alert-type-warning { + .angor-alert-container { + .angor-alert-border { + @apply bg-amber-500; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-amber-500; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-amber-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-amber-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + + /* Error */ + &.angor-alert-type-error { + .angor-alert-container { + .angor-alert-border { + @apply bg-red-600; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-red-700; + } + + .dark & { + @apply bg-gray-700; + + .angor-alert-border { + @apply bg-red-400; + } + + .angor-alert-title, + .angor-alert-icon { + @apply text-red-400; + } + + .angor-alert-message { + @apply text-gray-300; + } + + code { + @apply bg-gray-400 text-gray-800; + } + } + } + } + } + + /* Fill */ + &.angor-alert-appearance-fill { + .angor-alert-container { + border-radius: 6px; + + .angor-alert-dismiss-button { + @apply text-white; + } + } + + /* Primary */ + &.angor-alert-type-primary { + .angor-alert-container { + @apply bg-primary-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-primary-100; + } + + code { + @apply bg-primary-200 text-primary-800; + } + } + } + + /* Accent */ + &.angor-alert-type-accent { + .angor-alert-container { + @apply bg-accent-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-accent-100; + } + + code { + @apply bg-accent-200 text-accent-800; + } + } + } + + /* Warn */ + &.angor-alert-type-warn { + .angor-alert-container { + @apply bg-warn-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-warn-100; + } + + code { + @apply bg-warn-200 text-warn-800; + } + } + } + + /* Basic */ + &.angor-alert-type-basic { + .angor-alert-container { + @apply bg-gray-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-gray-100; + } + + code { + @apply bg-gray-200 text-gray-800; + } + } + } + + /* Info */ + &.angor-alert-type-info { + .angor-alert-container { + @apply bg-blue-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-blue-100; + } + + code { + @apply bg-blue-200 text-blue-800; + } + } + } + + /* Success */ + &.angor-alert-type-success { + .angor-alert-container { + @apply bg-green-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-green-100; + } + + code { + @apply bg-green-200 text-gray-800; + } + } + } + + /* Warning */ + &.angor-alert-type-warning { + .angor-alert-container { + @apply bg-amber-500; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-amber-100; + } + + code { + @apply bg-amber-200 text-amber-800; + } + } + } + + /* Error */ + &.angor-alert-type-error { + .angor-alert-container { + @apply bg-red-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title { + @apply text-white; + } + + .angor-alert-message { + @apply text-red-100; + } + + code { + @apply bg-red-200 text-red-800; + } + } + } + } + + /* Outline */ + &.angor-alert-appearance-outline { + .angor-alert-container { + border-radius: 6px; + } + + /* Primary */ + &.angor-alert-type-primary { + .angor-alert-container { + @apply bg-primary-50 ring-1 ring-inset ring-primary-400; + + .angor-alert-icon { + @apply text-primary-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-primary-900; + } + + .angor-alert-message { + @apply text-primary-700; + } + + code { + @apply bg-primary-200 text-primary-800; + } + + .dark & { + @apply bg-primary-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-primary-200; + } + } + } + } + + /* Accent */ + &.angor-alert-type-accent { + .angor-alert-container { + @apply bg-accent-100 ring-1 ring-inset ring-accent-400; + + .angor-alert-icon { + @apply text-accent-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-accent-900; + } + + .angor-alert-message { + @apply text-accent-700; + } + + code { + @apply bg-accent-200 text-accent-800; + } + + .dark & { + @apply bg-accent-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-accent-200; + } + } + } + } + + /* Warn */ + &.angor-alert-type-warn { + .angor-alert-container { + @apply bg-warn-50 ring-1 ring-inset ring-warn-400; + + .angor-alert-icon { + @apply text-warn-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-warn-900; + } + + .angor-alert-message { + @apply text-warn-700; + } + + code { + @apply bg-warn-200 text-warn-800; + } + + .dark & { + @apply bg-warn-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-warn-200; + } + } + } + } + + /* Basic */ + &.angor-alert-type-basic { + .angor-alert-container { + @apply bg-gray-100 ring-1 ring-inset ring-gray-400; + + .angor-alert-icon { + @apply text-gray-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-gray-900; + } + + .angor-alert-message { + @apply text-gray-700; + } + + code { + @apply bg-gray-200 text-gray-800; + } + + .dark & { + @apply bg-gray-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-gray-200; + } + } + } + } + + /* Info */ + &.angor-alert-type-info { + .angor-alert-container { + @apply bg-blue-50 ring-1 ring-inset ring-blue-400; + + .angor-alert-icon { + @apply text-blue-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-blue-900; + } + + .angor-alert-message { + @apply text-blue-700; + } + + code { + @apply bg-blue-200 text-blue-800; + } + + .dark & { + @apply bg-blue-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-blue-200; + } + } + } + } + + /* Success */ + &.angor-alert-type-success { + .angor-alert-container { + @apply bg-green-50 ring-1 ring-inset ring-green-400; + + .angor-alert-icon { + @apply text-green-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-green-900; + } + + .angor-alert-message { + @apply text-green-700; + } + + code { + @apply bg-green-200 text-green-800; + } + + .dark & { + @apply bg-green-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-green-200; + } + } + } + } + + /* Warning */ + &.angor-alert-type-warning { + .angor-alert-container { + @apply bg-amber-50 ring-1 ring-inset ring-amber-400; + + .angor-alert-icon { + @apply text-amber-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-amber-900; + } + + .angor-alert-message { + @apply text-amber-700; + } + + code { + @apply bg-amber-200 text-amber-800; + } + + .dark & { + @apply bg-amber-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-amber-200; + } + } + } + } + + /* Error */ + &.angor-alert-type-error { + .angor-alert-container { + @apply bg-red-50 ring-1 ring-inset ring-red-400; + + .angor-alert-icon { + @apply text-red-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-red-900; + } + + .angor-alert-message { + @apply text-red-700; + } + + code { + @apply bg-red-200 text-red-800; + } + + .dark & { + @apply bg-red-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-red-200; + } + } + } + } + } + + /* Soft */ + &.angor-alert-appearance-soft { + .angor-alert-container { + border-radius: 6px; + } + + /* Primary */ + &.angor-alert-type-primary { + .angor-alert-container { + @apply bg-primary-50; + + .angor-alert-icon { + @apply text-primary-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-primary-900; + } + + .angor-alert-message { + @apply text-primary-700; + } + + code { + @apply bg-primary-200 text-primary-800; + } + + .dark & { + @apply bg-primary-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-primary-200; + } + } + } + } + + /* Accent */ + &.angor-alert-type-accent { + .angor-alert-container { + @apply bg-accent-100; + + .angor-alert-icon { + @apply text-accent-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-accent-900; + } + + .angor-alert-message { + @apply text-accent-700; + } + + code { + @apply bg-accent-200 text-accent-800; + } + + .dark & { + @apply bg-accent-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-accent-200; + } + } + } + } + + /* Warn */ + &.angor-alert-type-warn { + .angor-alert-container { + @apply bg-warn-50; + + .angor-alert-icon { + @apply text-warn-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-warn-900; + } + + .angor-alert-message { + @apply text-warn-700; + } + + code { + @apply bg-warn-200 text-warn-800; + } + + .dark & { + @apply bg-warn-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-warn-200; + } + } + } + } + + /* Basic */ + &.angor-alert-type-basic { + .angor-alert-container { + @apply bg-gray-100; + + .angor-alert-icon { + @apply text-gray-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-gray-900; + } + + .angor-alert-message { + @apply text-gray-700; + } + + code { + @apply bg-gray-200 text-gray-800; + } + + .dark & { + @apply bg-gray-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-gray-200; + } + } + } + } + + /* Info */ + &.angor-alert-type-info { + .angor-alert-container { + @apply bg-blue-50; + + .angor-alert-icon { + @apply text-blue-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-blue-900; + } + + .angor-alert-message { + @apply text-blue-700; + } + + code { + @apply bg-blue-200 text-blue-800; + } + + .dark & { + @apply bg-blue-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-blue-200; + } + } + } + } + + /* Success */ + &.angor-alert-type-success { + .angor-alert-container { + @apply bg-green-50; + + .angor-alert-icon { + @apply text-green-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-green-900; + } + + .angor-alert-message { + @apply text-green-700; + } + + code { + @apply bg-green-200 text-green-800; + } + + .dark & { + @apply bg-green-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-green-200; + } + } + } + } + + /* Warning */ + &.angor-alert-type-warning { + .angor-alert-container { + @apply bg-amber-50; + + .angor-alert-icon { + @apply text-amber-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-amber-900; + } + + .angor-alert-message { + @apply text-amber-700; + } + + code { + @apply bg-amber-200 text-amber-800; + } + + .dark & { + @apply bg-amber-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-amber-200; + } + } + } + } + + /* Error */ + &.angor-alert-type-error { + .angor-alert-container { + @apply bg-red-50; + + .angor-alert-icon { + @apply text-red-600; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-red-900; + } + + .angor-alert-message { + @apply text-red-700; + } + + code { + @apply bg-red-200 text-red-800; + } + + .dark & { + @apply bg-red-600; + + .angor-alert-icon { + @apply text-white; + } + + .angor-alert-title, + .angor-alert-dismiss-button { + @apply text-white; + } + + .angor-alert-message { + @apply text-red-200; + } + } + } + } + } +} diff --git a/src/@angor/components/alert/alert.component.ts b/src/@angor/components/alert/alert.component.ts new file mode 100644 index 0000000..ea9ebec --- /dev/null +++ b/src/@angor/components/alert/alert.component.ts @@ -0,0 +1,167 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + SimpleChanges, + ViewEncapsulation, + inject, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertService } from '@angor/components/alert/alert.service'; +import { + AngorAlertAppearance, + AngorAlertType, +} from '@angor/components/alert/alert.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; +import { Subject, filter, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-alert', + templateUrl: './alert.component.html', + styleUrls: ['./alert.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + animations: angorAnimations, + exportAs: 'angorAlert', + standalone: true, + imports: [MatIconModule, MatButtonModule], +}) +export class AngorAlertComponent implements OnChanges, OnInit, OnDestroy { + static ngAcceptInputType_dismissible: BooleanInput; + static ngAcceptInputType_dismissed: BooleanInput; + static ngAcceptInputType_showIcon: BooleanInput; + + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorAlertService = inject(AngorAlertService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() appearance: AngorAlertAppearance = 'soft'; + @Input() dismissed: boolean = false; + @Input() dismissible: boolean = false; + @Input() name: string = this._angorUtilsService.randomId(); + @Input() showIcon: boolean = true; + @Input() type: AngorAlertType = 'primary'; + @Output() readonly dismissedChanged: EventEmitter = + new EventEmitter(); + + private _unsubscribeAll: Subject = new Subject(); + + /** + * Host binding for component classes. + */ + @HostBinding('class') get classList(): any { + return { + 'angor-alert-appearance-border': this.appearance === 'border', + 'angor-alert-appearance-fill': this.appearance === 'fill', + 'angor-alert-appearance-outline': this.appearance === 'outline', + 'angor-alert-appearance-soft': this.appearance === 'soft', + 'angor-alert-dismissed': this.dismissed, + 'angor-alert-dismissible': this.dismissible, + 'angor-alert-show-icon': this.showIcon, + 'angor-alert-type-primary': this.type === 'primary', + 'angor-alert-type-accent': this.type === 'accent', + 'angor-alert-type-warn': this.type === 'warn', + 'angor-alert-type-basic': this.type === 'basic', + 'angor-alert-type-info': this.type === 'info', + 'angor-alert-type-success': this.type === 'success', + 'angor-alert-type-warning': this.type === 'warning', + 'angor-alert-type-error': this.type === 'error', + }; + } + + /** + * Handles input changes and updates the component state accordingly. + * + * @param changes Input changes + */ + ngOnChanges(changes: SimpleChanges): void { + if ('dismissed' in changes) { + this.dismissed = coerceBooleanProperty(changes.dismissed.currentValue); + this._toggleDismiss(this.dismissed); + } + + if ('dismissible' in changes) { + this.dismissible = coerceBooleanProperty(changes.dismissible.currentValue); + } + + if ('showIcon' in changes) { + this.showIcon = coerceBooleanProperty(changes.showIcon.currentValue); + } + } + + /** + * Initializes component and subscribes to alert service events. + */ + ngOnInit(): void { + this._angorAlertService.onDismiss + .pipe( + filter((name) => this.name === name), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this.dismiss(); + }); + + this._angorAlertService.onShow + .pipe( + filter((name) => this.name === name), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this.show(); + }); + } + + /** + * Cleans up subscriptions to avoid memory leaks. + */ + ngOnDestroy(): void { + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + /** + * Dismisses the alert. + */ + dismiss(): void { + if (this.dismissed) { + return; + } + this._toggleDismiss(true); + } + + /** + * Shows the dismissed alert. + */ + show(): void { + if (!this.dismissed) { + return; + } + this._toggleDismiss(false); + } + + /** + * Toggles the dismissed state of the alert. + * + * @param dismissed Boolean indicating if the alert is dismissed + */ + private _toggleDismiss(dismissed: boolean): void { + if (!this.dismissible) { + return; + } + + this.dismissed = dismissed; + this.dismissedChanged.next(this.dismissed); + this._changeDetectorRef.markForCheck(); + } +} diff --git a/src/@angor/components/alert/alert.service.ts b/src/@angor/components/alert/alert.service.ts new file mode 100644 index 0000000..d563ac1 --- /dev/null +++ b/src/@angor/components/alert/alert.service.ts @@ -0,0 +1,43 @@ +import { Injectable } from '@angular/core'; +import { Observable, ReplaySubject } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AngorAlertService { + // Private subjects for managing alert actions + private readonly _onDismiss = new ReplaySubject(1); + private readonly _onShow = new ReplaySubject(1); + + /** + * Observable for dismissed alerts. + * @returns {Observable} - Stream of dismissed alerts. + */ + get onDismiss(): Observable { + return this._onDismiss.asObservable(); + } + + /** + * Observable for shown alerts. + * @returns {Observable} - Stream of shown alerts. + */ + get onShow(): Observable { + return this._onShow.asObservable(); + } + + /** + * Dismisses the alert with the specified name. + * @param {string} name - The name of the alert to dismiss. + */ + dismiss(name: string): void { + if (!name) return; // Exit if name is not provided + this._onDismiss.next(name); // Trigger dismiss action + } + + /** + * Shows the alert with the specified name. + * @param {string} name - The name of the alert to show. + */ + show(name: string): void { + if (!name) return; // Exit if name is not provided + this._onShow.next(name); // Trigger show action + } +} diff --git a/src/@angor/components/alert/alert.types.ts b/src/@angor/components/alert/alert.types.ts new file mode 100644 index 0000000..2fd5b41 --- /dev/null +++ b/src/@angor/components/alert/alert.types.ts @@ -0,0 +1,11 @@ +export type AngorAlertAppearance = 'border' | 'fill' | 'outline' | 'soft'; + +export type AngorAlertType = + | 'primary' + | 'accent' + | 'warn' + | 'basic' + | 'info' + | 'success' + | 'warning' + | 'error'; diff --git a/src/@angor/components/alert/index.ts b/src/@angor/components/alert/index.ts new file mode 100644 index 0000000..3d0c868 --- /dev/null +++ b/src/@angor/components/alert/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/alert/public-api'; diff --git a/src/@angor/components/alert/public-api.ts b/src/@angor/components/alert/public-api.ts new file mode 100644 index 0000000..59e483e --- /dev/null +++ b/src/@angor/components/alert/public-api.ts @@ -0,0 +1,3 @@ +export * from '@angor/components/alert/alert.component'; +export * from '@angor/components/alert/alert.service'; +export * from '@angor/components/alert/alert.types'; diff --git a/src/@angor/components/card/card.component.html b/src/@angor/components/card/card.component.html new file mode 100644 index 0000000..d233f44 --- /dev/null +++ b/src/@angor/components/card/card.component.html @@ -0,0 +1,25 @@ + +@if (flippable) { + +
+ +
+ + +
+ +
+} + + +@if (!flippable) { + + + + + @if (expanded) { +
+ +
+ } +} diff --git a/src/@angor/components/card/card.component.scss b/src/@angor/components/card/card.component.scss new file mode 100644 index 0000000..241add9 --- /dev/null +++ b/src/@angor/components/card/card.component.scss @@ -0,0 +1,65 @@ +angor-card { + position: relative; + display: flex; + overflow: hidden; + @apply bg-card rounded-2xl shadow; + + /* Flippable */ + &.angor-card-flippable { + border-radius: 0; + overflow: visible; + transform-style: preserve-3d; + transition: transform 1s; + perspective: 600px; + background: transparent; + @apply shadow-none; + + &.angor-card-face-back { + .angor-card-front { + visibility: hidden; + opacity: 0; + transform: rotateY(180deg); + } + + .angor-card-back { + visibility: visible; + opacity: 1; + transform: rotateY(360deg); + } + } + + .angor-card-front, + .angor-card-back { + display: flex; + flex-direction: column; + flex: 1 1 auto; + z-index: 10; + transition: + transform 0.5s ease-out 0s, + visibility 0s ease-in 0.2s, + opacity 0s ease-in 0.2s; + backface-visibility: hidden; + @apply bg-card rounded-2xl shadow; + } + + .angor-card-front { + position: relative; + opacity: 1; + visibility: visible; + transform: rotateY(0deg); + overflow: hidden; + } + + .angor-card-back { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + opacity: 0; + visibility: hidden; + transform: rotateY(180deg); + overflow: hidden auto; + } + } +} diff --git a/src/@angor/components/card/card.component.ts b/src/@angor/components/card/card.component.ts new file mode 100644 index 0000000..9d981be --- /dev/null +++ b/src/@angor/components/card/card.component.ts @@ -0,0 +1,57 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + Component, + HostBinding, + Input, + OnChanges, + SimpleChanges, + ViewEncapsulation, +} from '@angular/core'; +import { angorAnimations } from '@angor/animations'; +import { AngorCardFace } from '@angor/components/card/card.types'; + +@Component({ + selector: 'angor-card', + templateUrl: './card.component.html', + styleUrls: ['./card.component.scss'], + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + exportAs: 'angorCard', + standalone: true, + imports: [], +}) +export class AngorCardComponent implements OnChanges { + static ngAcceptInputType_expanded: BooleanInput; + static ngAcceptInputType_flippable: BooleanInput; + + @Input() expanded: boolean = false; + @Input() face: AngorCardFace = 'front'; + @Input() flippable: boolean = false; + + /** + * Host binding for component classes + */ + @HostBinding('class') get classList(): any { + return { + 'angor-card-expanded': this.expanded, + 'angor-card-face-back': this.flippable && this.face === 'back', + 'angor-card-face-front': this.flippable && this.face === 'front', + 'angor-card-flippable': this.flippable, + }; + } + + /** + * Handle input changes + * + * @param changes Input changes + */ + ngOnChanges(changes: SimpleChanges): void { + if ('expanded' in changes) { + this.expanded = coerceBooleanProperty(changes.expanded.currentValue); + } + + if ('flippable' in changes) { + this.flippable = coerceBooleanProperty(changes.flippable.currentValue); + } + } +} diff --git a/src/@angor/components/card/card.types.ts b/src/@angor/components/card/card.types.ts new file mode 100644 index 0000000..e3042ad --- /dev/null +++ b/src/@angor/components/card/card.types.ts @@ -0,0 +1 @@ +export type AngorCardFace = 'front' | 'back'; diff --git a/src/@angor/components/card/index.ts b/src/@angor/components/card/index.ts new file mode 100644 index 0000000..016131c --- /dev/null +++ b/src/@angor/components/card/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/card/public-api'; diff --git a/src/@angor/components/card/public-api.ts b/src/@angor/components/card/public-api.ts new file mode 100644 index 0000000..59e693b --- /dev/null +++ b/src/@angor/components/card/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/components/card/card.component'; diff --git a/src/@angor/components/drawer/drawer.component.html b/src/@angor/components/drawer/drawer.component.html new file mode 100644 index 0000000..3dea436 --- /dev/null +++ b/src/@angor/components/drawer/drawer.component.html @@ -0,0 +1,3 @@ +
+ +
diff --git a/src/@angor/components/drawer/drawer.component.scss b/src/@angor/components/drawer/drawer.component.scss new file mode 100644 index 0000000..8046c8a --- /dev/null +++ b/src/@angor/components/drawer/drawer.component.scss @@ -0,0 +1,132 @@ +/* Variables */ +:root { + --angor-drawer-width: 320px; +} + +angor-drawer { + position: relative; + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: var(--angor-drawer-width); + min-width: var(--angor-drawer-width); + max-width: var(--angor-drawer-width); + z-index: 300; + box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.35); + @apply bg-card; + + /* Animations */ + &.angor-drawer-animations-enabled { + transition-duration: 400ms; + transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); + transition-property: visibility, margin-left, margin-right, transform, + width, max-width, min-width; + + .angor-drawer-content { + transition-duration: 400ms; + transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); + transition-property: width, max-width, min-width; + } + } + + /* Over mode */ + &.angor-drawer-mode-over { + position: absolute; + top: 0; + bottom: 0; + + /* Fixed mode */ + &.angor-drawer-fixed { + position: fixed; + } + } + + /* Left position */ + &.angor-drawer-position-left { + /* Side mode */ + &.angor-drawer-mode-side { + margin-left: calc(var(--angor-drawer-width) * -1); + + &.angor-drawer-opened { + margin-left: 0; + } + } + + /* Over mode */ + &.angor-drawer-mode-over { + left: 0; + transform: translate3d(-100%, 0, 0); + + &.angor-drawer-opened { + transform: translate3d(0, 0, 0); + } + } + + /* Content */ + .angor-drawer-content { + left: 0; + } + } + + /* Right position */ + &.angor-drawer-position-right { + /* Side mode */ + &.angor-drawer-mode-side { + margin-right: calc(var(--angor-drawer-width) * -1); + + &.angor-drawer-opened { + margin-right: 0; + } + } + + /* Over mode */ + &.angor-drawer-mode-over { + right: 0; + transform: translate3d(100%, 0, 0); + + &.angor-drawer-opened { + transform: translate3d(0, 0, 0); + } + } + + /* Content */ + .angor-drawer-content { + right: 0; + } + } + + /* Content */ + .angor-drawer-content { + position: absolute; + display: flex; + flex: 1 1 auto; + top: 0; + bottom: 0; + width: 100%; + height: 100%; + overflow: hidden; + @apply bg-card; + } +} + +/* Overlay */ +.angor-drawer-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 299; + opacity: 1; + background-color: rgba(0, 0, 0, 0.6); + + /* Fixed mode */ + &.angor-drawer-overlay-fixed { + position: fixed; + } + + /* Transparent overlay */ + &.angor-drawer-overlay-transparent { + background-color: transparent; + } +} diff --git a/src/@angor/components/drawer/drawer.component.ts b/src/@angor/components/drawer/drawer.component.ts new file mode 100644 index 0000000..9eea79f --- /dev/null +++ b/src/@angor/components/drawer/drawer.component.ts @@ -0,0 +1,235 @@ +import { + animate, + AnimationBuilder, + AnimationPlayer, + style, +} from '@angular/animations'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + Renderer2, + SimpleChanges, + ViewEncapsulation, + inject, +} from '@angular/core'; +import { AngorDrawerService } from '@angor/components/drawer/drawer.service'; +import { + AngorDrawerMode, + AngorDrawerPosition, +} from '@angor/components/drawer/drawer.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; + +@Component({ + selector: 'angor-drawer', + templateUrl: './drawer.component.html', + styleUrls: ['./drawer.component.scss'], + encapsulation: ViewEncapsulation.None, + exportAs: 'angorDrawer', + standalone: true, +}) +export class AngorDrawerComponent implements OnChanges, OnInit, OnDestroy { + static ngAcceptInputType_fixed: BooleanInput; + static ngAcceptInputType_opened: BooleanInput; + static ngAcceptInputType_transparentOverlay: BooleanInput; + + private _animationBuilder = inject(AnimationBuilder); + private _elementRef = inject(ElementRef); + private _renderer2 = inject(Renderer2); + private _angorDrawerService = inject(AngorDrawerService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() fixed: boolean = false; + @Input() mode: AngorDrawerMode = 'side'; + @Input() name: string = this._angorUtilsService.randomId(); + @Input() opened: boolean = false; + @Input() position: AngorDrawerPosition = 'left'; + @Input() transparentOverlay: boolean = false; + @Output() readonly fixedChanged = new EventEmitter(); + @Output() readonly modeChanged = new EventEmitter(); + @Output() readonly openedChanged = new EventEmitter(); + @Output() readonly positionChanged = new EventEmitter(); + + private _animationsEnabled: boolean = false; + private _hovered: boolean = false; + private _overlay: HTMLElement; + private _player: AnimationPlayer; + + @HostBinding('class') get classList(): any { + return { + 'angor-drawer-animations-enabled': this._animationsEnabled, + 'angor-drawer-fixed': this.fixed, + 'angor-drawer-hover': this._hovered, + [`angor-drawer-mode-${this.mode}`]: true, + 'angor-drawer-opened': this.opened, + [`angor-drawer-position-${this.position}`]: true, + }; + } + + @HostBinding('style') get styleList(): any { + return { + visibility: this.opened ? 'visible' : 'hidden', + }; + } + + @HostListener('mouseenter') + private _onMouseenter(): void { + this._enableAnimations(); + this._hovered = true; + } + + @HostListener('mouseleave') + private _onMouseleave(): void { + this._enableAnimations(); + this._hovered = false; + } + + ngOnChanges(changes: SimpleChanges): void { + if ('fixed' in changes) { + this.fixed = coerceBooleanProperty(changes.fixed.currentValue); + this.fixedChanged.next(this.fixed); + } + + if ('mode' in changes) { + const previousMode = changes.mode.previousValue; + const currentMode = changes.mode.currentValue; + this._disableAnimations(); + + if (previousMode === 'over' && currentMode === 'side') { + this._hideOverlay(); + } + + if (previousMode === 'side' && currentMode === 'over' && this.opened) { + this._showOverlay(); + } + + this.modeChanged.next(currentMode); + setTimeout(() => this._enableAnimations(), 500); + } + + if ('opened' in changes) { + const open = coerceBooleanProperty(changes.opened.currentValue); + this._toggleOpened(open); + } + + if ('position' in changes) { + this.positionChanged.next(this.position); + } + + if ('transparentOverlay' in changes) { + this.transparentOverlay = coerceBooleanProperty(changes.transparentOverlay.currentValue); + } + } + + ngOnInit(): void { + this._angorDrawerService.registerComponent(this.name, this); + } + + ngOnDestroy(): void { + if (this._player) { + this._player.finish(); + } + this._angorDrawerService.deregisterComponent(this.name); + } + + open(): void { + if (this.opened) return; + this._toggleOpened(true); + } + + close(): void { + if (!this.opened) return; + this._toggleOpened(false); + } + + toggle(): void { + this.opened ? this.close() : this.open(); + } + + private _enableAnimations(): void { + if (!this._animationsEnabled) { + this._animationsEnabled = true; + } + } + + private _disableAnimations(): void { + if (this._animationsEnabled) { + this._animationsEnabled = false; + } + } + + private _showOverlay(): void { + this._overlay = this._renderer2.createElement('div'); + this._overlay.classList.add('angor-drawer-overlay'); + if (this.fixed) { + this._overlay.classList.add('angor-drawer-overlay-fixed'); + } + if (this.transparentOverlay) { + this._overlay.classList.add('angor-drawer-overlay-transparent'); + } + + this._renderer2.appendChild( + this._elementRef.nativeElement.parentElement, + this._overlay + ); + + this._player = this._animationBuilder + .build([ + style({ opacity: 0 }), + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 1 }) + ), + ]) + .create(this._overlay); + + this._player.play(); + this._overlay.addEventListener('click', this._handleOverlayClick); + } + + private _hideOverlay(): void { + if (!this._overlay) return; + + this._player = this._animationBuilder + .build([ + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 0 }) + ), + ]) + .create(this._overlay); + + this._player.play(); + this._player.onDone(() => { + if (this._overlay) { + this._overlay.removeEventListener( + 'click', + this._handleOverlayClick + ); + this._overlay.parentNode.removeChild(this._overlay); + this._overlay = null; + } + }); + } + + private _toggleOpened(open: boolean): void { + this.opened = open; + this._enableAnimations(); + + if (this.mode === 'over') { + open ? this._showOverlay() : this._hideOverlay(); + } + + this.openedChanged.next(open); + } + + private readonly _handleOverlayClick = (): void => this.close(); +} diff --git a/src/@angor/components/drawer/drawer.service.ts b/src/@angor/components/drawer/drawer.service.ts new file mode 100644 index 0000000..daa219b --- /dev/null +++ b/src/@angor/components/drawer/drawer.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import { AngorDrawerComponent } from '@angor/components/drawer/drawer.component'; + +@Injectable({ providedIn: 'root' }) +export class AngorDrawerService { + private _componentRegistry: Map = new Map< + string, + AngorDrawerComponent + >(); + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register drawer component + * + * @param name + * @param component + */ + registerComponent(name: string, component: AngorDrawerComponent): void { + this._componentRegistry.set(name, component); + } + + /** + * Deregister drawer component + * + * @param name + */ + deregisterComponent(name: string): void { + this._componentRegistry.delete(name); + } + + /** + * Get drawer component from the registry + * + * @param name + */ + getComponent(name: string): AngorDrawerComponent | undefined { + return this._componentRegistry.get(name); + } +} diff --git a/src/@angor/components/drawer/drawer.types.ts b/src/@angor/components/drawer/drawer.types.ts new file mode 100644 index 0000000..91360b9 --- /dev/null +++ b/src/@angor/components/drawer/drawer.types.ts @@ -0,0 +1,3 @@ +export type AngorDrawerMode = 'over' | 'side'; + +export type AngorDrawerPosition = 'left' | 'right'; diff --git a/src/@angor/components/drawer/index.ts b/src/@angor/components/drawer/index.ts new file mode 100644 index 0000000..fc055b3 --- /dev/null +++ b/src/@angor/components/drawer/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/drawer/public-api'; diff --git a/src/@angor/components/drawer/public-api.ts b/src/@angor/components/drawer/public-api.ts new file mode 100644 index 0000000..7f3119c --- /dev/null +++ b/src/@angor/components/drawer/public-api.ts @@ -0,0 +1,3 @@ +export * from '@angor/components/drawer/drawer.component'; +export * from '@angor/components/drawer/drawer.service'; +export * from '@angor/components/drawer/drawer.types'; diff --git a/src/@angor/components/fullscreen/fullscreen.component.html b/src/@angor/components/fullscreen/fullscreen.component.html new file mode 100644 index 0000000..038fc14 --- /dev/null +++ b/src/@angor/components/fullscreen/fullscreen.component.html @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/@angor/components/fullscreen/fullscreen.component.ts b/src/@angor/components/fullscreen/fullscreen.component.ts new file mode 100644 index 0000000..613a535 --- /dev/null +++ b/src/@angor/components/fullscreen/fullscreen.component.ts @@ -0,0 +1,54 @@ +import { DOCUMENT, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + Input, + TemplateRef, + ViewEncapsulation, + inject, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; + +@Component({ + selector: 'angor-fullscreen', + templateUrl: './fullscreen.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'angorFullscreen', + standalone: true, + imports: [ + MatButtonModule, + MatTooltipModule, + NgTemplateOutlet, + MatIconModule, + ], +}) +export class AngorFullscreenComponent { + private _document = inject(DOCUMENT); + + @Input() iconTpl: TemplateRef; + @Input() tooltip: string; + + /** + * Toggles the fullscreen mode. + */ + toggleFullscreen(): void { + if (!this._document.fullscreenEnabled) { + console.log('Fullscreen is not available in this browser.'); + return; + } + + const fullScreen = this._document.fullscreenElement; + + // Toggle fullscreen based on the current state + if (fullScreen) { + this._document.exitFullscreen(); + } else { + this._document.documentElement.requestFullscreen().catch(() => { + console.error('Entering fullscreen mode failed.'); + }); + } + } +} diff --git a/src/@angor/components/fullscreen/index.ts b/src/@angor/components/fullscreen/index.ts new file mode 100644 index 0000000..6a946ba --- /dev/null +++ b/src/@angor/components/fullscreen/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/fullscreen/public-api'; diff --git a/src/@angor/components/fullscreen/public-api.ts b/src/@angor/components/fullscreen/public-api.ts new file mode 100644 index 0000000..c76bd4d --- /dev/null +++ b/src/@angor/components/fullscreen/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/components/fullscreen/fullscreen.component'; diff --git a/src/@angor/components/highlight/highlight.component.html b/src/@angor/components/highlight/highlight.component.html new file mode 100644 index 0000000..545a009 --- /dev/null +++ b/src/@angor/components/highlight/highlight.component.html @@ -0,0 +1,8 @@ + + + + +
+
+
+
diff --git a/src/@angor/components/highlight/highlight.component.scss b/src/@angor/components/highlight/highlight.component.scss new file mode 100644 index 0000000..9475ea7 --- /dev/null +++ b/src/@angor/components/highlight/highlight.component.scss @@ -0,0 +1,3 @@ +textarea[angor-highlight] { + display: none; +} diff --git a/src/@angor/components/highlight/highlight.component.ts b/src/@angor/components/highlight/highlight.component.ts new file mode 100644 index 0000000..0bf3b27 --- /dev/null +++ b/src/@angor/components/highlight/highlight.component.ts @@ -0,0 +1,81 @@ +import { NgClass } from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + Component, + ElementRef, + EmbeddedViewRef, + inject, + Input, + OnChanges, + SecurityContext, + SimpleChanges, + TemplateRef, + ViewChild, + ViewContainerRef, + ViewEncapsulation, +} from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { AngorHighlightService } from '@angor/components/highlight/highlight.service'; + +@Component({ + selector: 'textarea[angor-highlight]', + templateUrl: './highlight.component.html', + styleUrls: ['./highlight.component.scss'], + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'angorHighlight', + standalone: true, + imports: [NgClass], +}) +export class AngorHighlightComponent implements OnChanges, AfterViewInit { + private _domSanitizer = inject(DomSanitizer); + private _elementRef = inject(ElementRef); + private _angorHighlightService = inject(AngorHighlightService); + private _viewContainerRef = inject(ViewContainerRef); + + @Input() code: string; + @Input() lang: string; + @ViewChild(TemplateRef) templateRef: TemplateRef; + + highlightedCode: string; + private _viewRef: EmbeddedViewRef; + + ngOnChanges(changes: SimpleChanges): void { + if ('code' in changes || 'lang' in changes) { + if (!this._viewContainerRef.length) return; + this._highlightAndInsert(); + } + } + + ngAfterViewInit(): void { + if (!this.lang) return; + if (!this.code) { + this.code = this._elementRef.nativeElement.value; + } + this._highlightAndInsert(); + } + + private _highlightAndInsert(): void { + if (!this.templateRef || !this.code || !this.lang) return; + + if (this._viewRef) { + this._viewRef.destroy(); + this._viewRef = null; + } + + this.highlightedCode = this._domSanitizer.sanitize( + SecurityContext.HTML, + this._angorHighlightService.highlight(this.code, this.lang) + ); + + if (this.highlightedCode === null) return; + + this._viewRef = this._viewContainerRef.createEmbeddedView( + this.templateRef, + { highlightedCode: this.highlightedCode, lang: this.lang } + ); + + this._viewRef.detectChanges(); + } +} diff --git a/src/@angor/components/highlight/highlight.service.ts b/src/@angor/components/highlight/highlight.service.ts new file mode 100644 index 0000000..ddd6028 --- /dev/null +++ b/src/@angor/components/highlight/highlight.service.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@angular/core'; +import hljs from 'highlight.js'; + +@Injectable({ providedIn: 'root' }) +export class AngorHighlightService { + + /** + * Highlights the provided code using the specified language. + */ + highlight(code: string, language: string): string { + code = this._format(code); // Format the code + return hljs.highlight(code, { language }).value; // Highlight and return the formatted code + } + + /** + * Removes empty lines around the code block and re-aligns indentation based on the first non-whitespace character. + */ + private _format(code: string): string { + let indentation = 0; + const lines = code.split('\n'); // Split the code into lines + + // Remove empty lines at the start and end + while (lines.length && lines[0].trim() === '') { + lines.shift(); + } + while (lines.length && lines[lines.length - 1].trim() === '') { + lines.pop(); + } + + // Determine the smallest indentation + lines.filter(line => line.length).forEach((line, index) => { + if (index === 0) { + indentation = line.search(/\S|$/); + } else { + indentation = Math.min(line.search(/\S|$/), indentation); + } + }); + + // Remove extra indentation and return formatted code + return lines.map(line => line.substring(indentation)).join('\n'); + } +} diff --git a/src/@angor/components/highlight/index.ts b/src/@angor/components/highlight/index.ts new file mode 100644 index 0000000..0cf5adf --- /dev/null +++ b/src/@angor/components/highlight/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/highlight/public-api'; diff --git a/src/@angor/components/highlight/public-api.ts b/src/@angor/components/highlight/public-api.ts new file mode 100644 index 0000000..27d4d63 --- /dev/null +++ b/src/@angor/components/highlight/public-api.ts @@ -0,0 +1,2 @@ +export * from '@angor/components/highlight/highlight.component'; +export * from '@angor/components/highlight/highlight.service'; diff --git a/src/@angor/components/loading-bar/index.ts b/src/@angor/components/loading-bar/index.ts new file mode 100644 index 0000000..99095f5 --- /dev/null +++ b/src/@angor/components/loading-bar/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/loading-bar/public-api'; diff --git a/src/@angor/components/loading-bar/loading-bar.component.html b/src/@angor/components/loading-bar/loading-bar.component.html new file mode 100644 index 0000000..0500342 --- /dev/null +++ b/src/@angor/components/loading-bar/loading-bar.component.html @@ -0,0 +1,3 @@ +@if (show) { + +} diff --git a/src/@angor/components/loading-bar/loading-bar.component.scss b/src/@angor/components/loading-bar/loading-bar.component.scss new file mode 100644 index 0000000..7244773 --- /dev/null +++ b/src/@angor/components/loading-bar/loading-bar.component.scss @@ -0,0 +1,7 @@ +angor-loading-bar { + position: fixed; + top: 0; + z-index: 999; + width: 100%; + height: 6px; +} diff --git a/src/@angor/components/loading-bar/loading-bar.component.ts b/src/@angor/components/loading-bar/loading-bar.component.ts new file mode 100644 index 0000000..19ca3ce --- /dev/null +++ b/src/@angor/components/loading-bar/loading-bar.component.ts @@ -0,0 +1,57 @@ +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Component, inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, ViewEncapsulation } from '@angular/core'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { AngorLoadingService } from '@angor/services/loading'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-loading-bar', + templateUrl: './loading-bar.component.html', + styleUrls: ['./loading-bar.component.scss'], + encapsulation: ViewEncapsulation.None, + exportAs: 'angorLoadingBar', + standalone: true, + imports: [MatProgressBarModule], +}) +export class AngorLoadingBarComponent implements OnChanges, OnInit, OnDestroy { + private _angorLoadingService = inject(AngorLoadingService); + + @Input() autoMode: boolean = true; + mode: 'determinate' | 'indeterminate'; + progress: number = 0; + show: boolean = false; + private _unsubscribeAll: Subject = new Subject(); + + ngOnChanges(changes: SimpleChanges): void { + if ('autoMode' in changes) { + this._angorLoadingService.setAutoMode( + coerceBooleanProperty(changes.autoMode.currentValue) + ); + } + } + + ngOnInit(): void { + this._angorLoadingService.mode$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((value) => { + this.mode = value; + }); + + this._angorLoadingService.progress$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((value) => { + this.progress = value; + }); + + this._angorLoadingService.show$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((value) => { + this.show = value; + }); + } + + ngOnDestroy(): void { + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/loading-bar/public-api.ts b/src/@angor/components/loading-bar/public-api.ts new file mode 100644 index 0000000..8450e32 --- /dev/null +++ b/src/@angor/components/loading-bar/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/components/loading-bar/loading-bar.component'; diff --git a/src/@angor/components/masonry/index.ts b/src/@angor/components/masonry/index.ts new file mode 100644 index 0000000..dcfbb72 --- /dev/null +++ b/src/@angor/components/masonry/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/masonry/public-api'; diff --git a/src/@angor/components/masonry/masonry.component.html b/src/@angor/components/masonry/masonry.component.html new file mode 100644 index 0000000..5bd5731 --- /dev/null +++ b/src/@angor/components/masonry/masonry.component.html @@ -0,0 +1,8 @@ +
+ +
diff --git a/src/@angor/components/masonry/masonry.component.ts b/src/@angor/components/masonry/masonry.component.ts new file mode 100644 index 0000000..f3a99cd --- /dev/null +++ b/src/@angor/components/masonry/masonry.component.ts @@ -0,0 +1,52 @@ +import { NgTemplateOutlet } from '@angular/common'; +import { + AfterViewInit, + Component, + Input, + OnChanges, + SimpleChanges, + TemplateRef, + ViewEncapsulation, +} from '@angular/core'; +import { angorAnimations } from '@angor/animations'; + +@Component({ + selector: 'angor-masonry', + templateUrl: './masonry.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + exportAs: 'angorMasonry', + standalone: true, + imports: [NgTemplateOutlet], +}) +export class AngorMasonryComponent implements OnChanges, AfterViewInit { + @Input() columnsTemplate: TemplateRef; + @Input() columns: number; + @Input() items: any[] = []; + distributedColumns: any[] = []; + + ngOnChanges(changes: SimpleChanges): void { + if ('columns' in changes || 'items' in changes) { + this._distributeItems(); + } + } + + ngAfterViewInit(): void { + this._distributeItems(); + } + + private _distributeItems(): void { + if (this.items.length === 0) { + this.distributedColumns = []; + return; + } + + this.distributedColumns = Array.from({ length: this.columns }, () => ({ + items: [], + })); + + this.items.forEach((item, index) => { + this.distributedColumns[index % this.columns].items.push(item); + }); + } +} diff --git a/src/@angor/components/masonry/public-api.ts b/src/@angor/components/masonry/public-api.ts new file mode 100644 index 0000000..fc40f6d --- /dev/null +++ b/src/@angor/components/masonry/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/components/masonry/masonry.component'; diff --git a/src/@angor/components/navigation/horizontal/components/basic/basic.component.html b/src/@angor/components/navigation/horizontal/components/basic/basic.component.html new file mode 100644 index 0000000..a893f25 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/basic/basic.component.html @@ -0,0 +1,149 @@ + +
+ + @if (item.link && !item.externalLink && !item.function && !item.disabled) { +
+ +
+ } + + + @if (item.link && item.externalLink && !item.function && !item.disabled) { + + + + } + + + @if (!item.link && item.function && !item.disabled) { +
+ +
+ } + + + @if (item.link && !item.externalLink && item.function && !item.disabled) { +
+ +
+ } + + + @if (item.link && item.externalLink && item.function && !item.disabled) { + + + + } + + + @if (!item.link && !item.function && !item.disabled) { +
+ +
+ } + + + @if (item.disabled) { +
+ +
+ } +
+ + + + + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } +
diff --git a/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts b/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts new file mode 100644 index 0000000..c92fad1 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/basic/basic.component.ts @@ -0,0 +1,100 @@ +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { + IsActiveMatchOptions, + RouterLink, + RouterLinkActive, +} from '@angular/router'; +import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-horizontal-navigation-basic-item', + templateUrl: './basic.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + RouterLink, + RouterLinkActive, + MatTooltipModule, + NgTemplateOutlet, + MatMenuModule, + MatIconModule, + ], +}) +export class AngorHorizontalNavigationBasicItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + // Set the equivalent of {exact: false} as default for active match options. + // We are not assigning the item.isActiveMatchOptions directly to the + // [routerLinkActiveOptions] because if it's "undefined" initially, the router + // will throw an error and stop working. + isActiveMatchOptions: IsActiveMatchOptions = + this._angorUtilsService.subsetMatchOptions; + + private _angorHorizontalNavigationComponent: AngorHorizontalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Set the "isActiveMatchOptions" either from item's + // "isActiveMatchOptions" or the equivalent form of + // item's "exactMatch" option + this.isActiveMatchOptions = + this.item.isActiveMatchOptions ?? this.item.exactMatch + ? this._angorUtilsService.exactMatchOptions + : this._angorUtilsService.subsetMatchOptions; + + // Get the parent navigation component + this._angorHorizontalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Subscribe to onRefreshed on the navigation component + this._angorHorizontalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/horizontal/components/branch/branch.component.html b/src/@angor/components/navigation/horizontal/components/branch/branch.component.html new file mode 100644 index 0000000..2dc5797 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/branch/branch.component.html @@ -0,0 +1,135 @@ +@if (!child) { +
+ +
+} + + + @for (item of item.children; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'basic') { +
+ +
+ } + + + @if ( + item.type === 'aside' || + item.type === 'collapsable' || + item.type === 'group' + ) { +
+ + +
+ } + + + @if (item.type === 'divider') { +
+ +
+ } + } + } +
+ + + +
+
+ + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } +
+
+
diff --git a/src/@angor/components/navigation/horizontal/components/branch/branch.component.ts b/src/@angor/components/navigation/horizontal/components/branch/branch.component.ts new file mode 100644 index 0000000..40e0cf4 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/branch/branch.component.ts @@ -0,0 +1,109 @@ +import { BooleanInput } from '@angular/cdk/coercion'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + ViewChild, + forwardRef, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenu, MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { AngorHorizontalNavigationBasicItemComponent } from '@angor/components/navigation/horizontal/components/basic/basic.component'; +import { AngorHorizontalNavigationDividerItemComponent } from '@angor/components/navigation/horizontal/components/divider/divider.component'; +import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-horizontal-navigation-branch-item', + templateUrl: './branch.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + MatMenuModule, + NgTemplateOutlet, + AngorHorizontalNavigationBasicItemComponent, + forwardRef(() => AngorHorizontalNavigationBranchItemComponent), + AngorHorizontalNavigationDividerItemComponent, + MatTooltipModule, + MatIconModule, + ], +}) +export class AngorHorizontalNavigationBranchItemComponent + implements OnInit, OnDestroy +{ + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_child: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() child: boolean = false; + @Input() item: AngorNavigationItem; + @Input() name: string; + @ViewChild('matMenu', { static: true }) matMenu: MatMenu; + + private _angorHorizontalNavigationComponent: AngorHorizontalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorHorizontalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorHorizontalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Trigger the change detection + */ + triggerChangeDetection(): void { + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/@angor/components/navigation/horizontal/components/divider/divider.component.html b/src/@angor/components/navigation/horizontal/components/divider/divider.component.html new file mode 100644 index 0000000..1713c62 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/divider/divider.component.html @@ -0,0 +1,5 @@ + +
diff --git a/src/@angor/components/navigation/horizontal/components/divider/divider.component.ts b/src/@angor/components/navigation/horizontal/components/divider/divider.component.ts new file mode 100644 index 0000000..ceb8a7c --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/divider/divider.component.ts @@ -0,0 +1,64 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-horizontal-navigation-divider-item', + templateUrl: './divider.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [NgClass], +}) +export class AngorHorizontalNavigationDividerItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + private _angorHorizontalNavigationComponent: AngorHorizontalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorHorizontalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorHorizontalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.html b/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.html new file mode 100644 index 0000000..7e2c3e9 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.html @@ -0,0 +1,5 @@ + +
diff --git a/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.ts b/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.ts new file mode 100644 index 0000000..85f7023 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/components/spacer/spacer.component.ts @@ -0,0 +1,64 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { AngorHorizontalNavigationComponent } from '@angor/components/navigation/horizontal/horizontal.component'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-horizontal-navigation-spacer-item', + templateUrl: './spacer.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [NgClass], +}) +export class AngorHorizontalNavigationSpacerItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + private _angorHorizontalNavigationComponent: AngorHorizontalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorHorizontalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorHorizontalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/horizontal/horizontal.component.html b/src/@angor/components/navigation/horizontal/horizontal.component.html new file mode 100644 index 0000000..7ae243e --- /dev/null +++ b/src/@angor/components/navigation/horizontal/horizontal.component.html @@ -0,0 +1,37 @@ +
+ @for (item of navigation; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'basic') { + + } + + + @if ( + item.type === 'aside' || + item.type === 'collapsable' || + item.type === 'group' + ) { + + } + + + @if (item.type === 'spacer') { + + } + } + } +
diff --git a/src/@angor/components/navigation/horizontal/horizontal.component.scss b/src/@angor/components/navigation/horizontal/horizontal.component.scss new file mode 100644 index 0000000..7c68820 --- /dev/null +++ b/src/@angor/components/navigation/horizontal/horizontal.component.scss @@ -0,0 +1,166 @@ +/* Root navigation specific */ +angor-horizontal-navigation { + .angor-horizontal-navigation-wrapper { + display: flex; + align-items: center; + + /* Basic, Branch */ + angor-horizontal-navigation-basic-item, + angor-horizontal-navigation-branch-item { + @screen sm { + &:hover { + .angor-horizontal-navigation-item-wrapper { + @apply bg-hover; + } + } + } + + .angor-horizontal-navigation-item-wrapper { + border-radius: 4px; + overflow: hidden; + + .angor-horizontal-navigation-item { + padding: 0 16px; + cursor: pointer; + user-select: none; + + .angor-horizontal-navigation-item-icon { + margin-right: 12px; + } + } + } + } + + /* Basic - When item active (current link) */ + angor-horizontal-navigation-basic-item { + .angor-horizontal-navigation-item-active, + .angor-horizontal-navigation-item-active-forced { + .angor-horizontal-navigation-item-title { + @apply text-primary #{'!important'}; + } + + .angor-horizontal-navigation-item-subtitle { + @apply text-primary-400 #{'!important'}; + + .dark & { + @apply text-primary-600 #{'!important'}; + } + } + + .angor-horizontal-navigation-item-icon { + @apply text-primary #{'!important'}; + } + } + } + + /* Branch - When menu open */ + angor-horizontal-navigation-branch-item { + .angor-horizontal-navigation-menu-active, + .angor-horizontal-navigation-menu-active-forced { + .angor-horizontal-navigation-item-wrapper { + @apply bg-hover; + } + } + } + + /* Spacer */ + angor-horizontal-navigation-spacer-item { + margin: 12px 0; + } + } +} + +/* Menu panel specific */ +.angor-horizontal-navigation-menu-panel { + .angor-horizontal-navigation-menu-item { + height: auto; + min-height: 0; + line-height: normal; + white-space: normal; + + /* Basic, Branch */ + angor-horizontal-navigation-basic-item, + angor-horizontal-navigation-branch-item, + angor-horizontal-navigation-divider-item { + display: flex; + flex: 1 1 auto; + } + + /* Divider */ + angor-horizontal-navigation-divider-item { + margin: 8px -16px; + + .angor-horizontal-navigation-item-wrapper { + height: 1px; + box-shadow: 0 1px 0 0; + } + } + } +} + +/* Navigation menu item common */ +.angor-horizontal-navigation-menu-item { + /* Basic - When item active (current link) */ + angor-horizontal-navigation-basic-item { + .angor-horizontal-navigation-item-active, + .angor-horizontal-navigation-item-active-forced { + .angor-horizontal-navigation-item-title { + @apply text-primary #{'!important'}; + } + + .angor-horizontal-navigation-item-subtitle { + @apply text-primary-400 #{'!important'}; + + .dark & { + @apply text-primary-600 #{'!important'}; + } + } + + .angor-horizontal-navigation-item-icon { + @apply text-primary #{'!important'}; + } + } + } + + .angor-horizontal-navigation-item-wrapper { + width: 100%; + + &.angor-horizontal-navigation-item-has-subtitle { + .angor-horizontal-navigation-item { + min-height: 56px; + } + } + + .angor-horizontal-navigation-item { + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + min-height: 48px; + width: 100%; + font-size: 13px; + font-weight: 500; + text-decoration: none; + + .angor-horizontal-navigation-item-title-wrapper { + .angor-horizontal-navigation-item-subtitle { + font-size: 12px; + } + } + + .angor-horizontal-navigation-item-badge { + margin-left: auto; + + .angor-horizontal-navigation-item-badge-content { + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 600; + white-space: nowrap; + height: 20px; + } + } + } + } +} diff --git a/src/@angor/components/navigation/horizontal/horizontal.component.ts b/src/@angor/components/navigation/horizontal/horizontal.component.ts new file mode 100644 index 0000000..1255ddf --- /dev/null +++ b/src/@angor/components/navigation/horizontal/horizontal.component.ts @@ -0,0 +1,116 @@ +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + ViewEncapsulation, + inject, +} from '@angular/core'; +import { angorAnimations } from '@angor/animations'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; +import { ReplaySubject, Subject } from 'rxjs'; +import { AngorHorizontalNavigationBasicItemComponent } from './components/basic/basic.component'; +import { AngorHorizontalNavigationBranchItemComponent } from './components/branch/branch.component'; +import { AngorHorizontalNavigationSpacerItemComponent } from './components/spacer/spacer.component'; + +@Component({ + selector: 'angor-horizontal-navigation', + templateUrl: './horizontal.component.html', + styleUrls: ['./horizontal.component.scss'], + animations: angorAnimations, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'angorHorizontalNavigation', + standalone: true, + imports: [ + AngorHorizontalNavigationBasicItemComponent, + AngorHorizontalNavigationBranchItemComponent, + AngorHorizontalNavigationSpacerItemComponent, + ], +}) +export class AngorHorizontalNavigationComponent + implements OnChanges, OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() name: string = this._angorUtilsService.randomId(); + @Input() navigation: AngorNavigationItem[]; + + onRefreshed: ReplaySubject = new ReplaySubject(1); + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On changes + * + * @param changes + */ + ngOnChanges(changes: SimpleChanges): void { + // Navigation + if ('navigation' in changes) { + // Mark for check + this._changeDetectorRef.markForCheck(); + } + } + + /** + * On init + */ + ngOnInit(): void { + // Make sure the name input is not an empty string + if (this.name === '') { + this.name = this._angorUtilsService.randomId(); + } + + // Register the navigation component + this._angorNavigationService.registerComponent(this.name, this); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Deregister the navigation component from the registry + this._angorNavigationService.deregisterComponent(this.name); + + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Refresh the component to apply the changes + */ + refresh(): void { + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Execute the observable + this.onRefreshed.next(true); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/@angor/components/navigation/index.ts b/src/@angor/components/navigation/index.ts new file mode 100644 index 0000000..aa3d19a --- /dev/null +++ b/src/@angor/components/navigation/index.ts @@ -0,0 +1 @@ +export * from '@angor/components/navigation/public-api'; diff --git a/src/@angor/components/navigation/navigation.service.ts b/src/@angor/components/navigation/navigation.service.ts new file mode 100644 index 0000000..0d913fd --- /dev/null +++ b/src/@angor/components/navigation/navigation.service.ts @@ -0,0 +1,129 @@ +import { Injectable } from '@angular/core'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; + +@Injectable({ providedIn: 'root' }) +export class AngorNavigationService { + private _componentRegistry = new Map(); + private _navigationStore = new Map(); + + /** + * Register navigation component + * + * @param name + * @param component + */ + registerComponent(name: string, component: any): void { + this._componentRegistry.set(name, component); + } + + /** + * Deregister navigation component + * + * @param name + */ + deregisterComponent(name: string): void { + this._componentRegistry.delete(name); + } + + /** + * Get navigation component from the registry + * + * @param name + */ + getComponent(name: string): T { + return this._componentRegistry.get(name); + } + + /** + * Store the given navigation with the given key + * + * @param key + * @param navigation + */ + storeNavigation(key: string, navigation: AngorNavigationItem[]): void { + this._navigationStore.set(key, navigation); + } + + /** + * Get navigation from storage by key + * + * @param key + */ + getNavigation(key: string): AngorNavigationItem[] { + return this._navigationStore.get(key) ?? []; + } + + /** + * Delete the navigation from the storage + * + * @param key + */ + deleteNavigation(key: string): void { + if (!this._navigationStore.has(key)) { + console.warn(`Navigation with the key '${key}' does not exist.`); + } + this._navigationStore.delete(key); + } + + /** + * Utility function that returns a flattened + * version of the given navigation array + * + * @param navigation + * @param flatNavigation + */ + getFlatNavigation( + navigation: AngorNavigationItem[], + flatNavigation: AngorNavigationItem[] = [] + ): AngorNavigationItem[] { + for (const item of navigation) { + if (item.type === 'basic') { + flatNavigation.push(item); + } else if (item.children) { + this.getFlatNavigation(item.children, flatNavigation); + } + } + return flatNavigation; + } + + /** + * Utility function that returns the item + * with the given id from given navigation + * + * @param id + * @param navigation + */ + getItem(id: string, navigation: AngorNavigationItem[]): AngorNavigationItem | null { + for (const item of navigation) { + if (item.id === id) return item; + if (item.children) { + const childItem = this.getItem(id, item.children); + if (childItem) return childItem; + } + } + return null; + } + + /** + * Utility function that returns the item's parent + * with the given id from given navigation + * + * @param id + * @param navigation + * @param parent + */ + getItemParent( + id: string, + navigation: AngorNavigationItem[], + parent: AngorNavigationItem[] | AngorNavigationItem + ): AngorNavigationItem[] | AngorNavigationItem | null { + for (const item of navigation) { + if (item.id === id) return parent; + if (item.children) { + const childItem = this.getItemParent(id, item.children, item); + if (childItem) return childItem; + } + } + return null; + } +} diff --git a/src/@angor/components/navigation/navigation.types.ts b/src/@angor/components/navigation/navigation.types.ts new file mode 100644 index 0000000..c6c5733 --- /dev/null +++ b/src/@angor/components/navigation/navigation.types.ts @@ -0,0 +1,43 @@ +import { + IsActiveMatchOptions, + Params, + QueryParamsHandling, +} from '@angular/router'; + +export interface AngorNavigationItem { + id?: string; + title?: string; + subtitle?: string; + type: 'aside' | 'basic' | 'collapsable' | 'divider' | 'group' | 'spacer'; + hidden?: (item: AngorNavigationItem) => boolean; + active?: boolean; + disabled?: boolean; + tooltip?: string; + link?: string; + fragment?: string; + preserveFragment?: boolean; + queryParams?: Params | null; + queryParamsHandling?: QueryParamsHandling | null; + externalLink?: boolean; + target?: '_blank' | '_self' | '_parent' | '_top' | string; + exactMatch?: boolean; + isActiveMatchOptions?: IsActiveMatchOptions; + function?: (item: AngorNavigationItem) => void; + classes?: { + title?: string; + subtitle?: string; + icon?: string; + wrapper?: string; + }; + icon?: string; + badge?: { + title?: string; + classes?: string; + }; + children?: AngorNavigationItem[]; + meta?: any; +} + +export type AngorVerticalNavigationAppearance = 'default' | 'compact' | 'dense' | 'thin'; +export type AngorVerticalNavigationMode = 'over' | 'side'; +export type AngorVerticalNavigationPosition = 'left' | 'right'; diff --git a/src/@angor/components/navigation/public-api.ts b/src/@angor/components/navigation/public-api.ts new file mode 100644 index 0000000..d6a8e51 --- /dev/null +++ b/src/@angor/components/navigation/public-api.ts @@ -0,0 +1,4 @@ +export * from '@angor/components/navigation/horizontal/horizontal.component'; +export * from '@angor/components/navigation/navigation.service'; +export * from '@angor/components/navigation/navigation.types'; +export * from '@angor/components/navigation/vertical/vertical.component'; diff --git a/src/@angor/components/navigation/vertical/components/aside/aside.component.html b/src/@angor/components/navigation/vertical/components/aside/aside.component.html new file mode 100644 index 0000000..0328aac --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/aside/aside.component.html @@ -0,0 +1,102 @@ +
+
+ + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } +
+
+ +@if (!skipChildren) { +
+ @for (item of item.children; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'basic') { + + } + + + @if (item.type === 'collapsable') { + + } + + + @if (item.type === 'divider') { + + } + + + @if (item.type === 'group') { + + } + + + @if (item.type === 'spacer') { + + } + } + } +
+} diff --git a/src/@angor/components/navigation/vertical/components/aside/aside.component.ts b/src/@angor/components/navigation/vertical/components/aside/aside.component.ts new file mode 100644 index 0000000..aa4d225 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/aside/aside.component.ts @@ -0,0 +1,203 @@ +import { BooleanInput } from '@angular/cdk/coercion'; +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { NavigationEnd, Router } from '@angular/router'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; +import { AngorVerticalNavigationCollapsableItemComponent } from '@angor/components/navigation/vertical/components/collapsable/collapsable.component'; +import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; +import { AngorVerticalNavigationGroupItemComponent } from '@angor/components/navigation/vertical/components/group/group.component'; +import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { Subject, filter, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-aside-item', + templateUrl: './aside.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + MatTooltipModule, + MatIconModule, + AngorVerticalNavigationBasicItemComponent, + AngorVerticalNavigationCollapsableItemComponent, + AngorVerticalNavigationDividerItemComponent, + AngorVerticalNavigationGroupItemComponent, + AngorVerticalNavigationSpacerItemComponent, + ], +}) +export class AngorVerticalNavigationAsideItemComponent + implements OnChanges, OnInit, OnDestroy +{ + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_autoCollapse: BooleanInput; + static ngAcceptInputType_skipChildren: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + private _changeDetectorRef = inject(ChangeDetectorRef); + private _router = inject(Router); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() activeItemId: string; + @Input() autoCollapse: boolean; + @Input() item: AngorNavigationItem; + @Input() name: string; + @Input() skipChildren: boolean; + + active: boolean = false; + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On changes + * + * @param changes + */ + ngOnChanges(changes: SimpleChanges): void { + // Active item id + if ('activeItemId' in changes) { + // Mark if active + this._markIfActive(this._router.url); + } + } + + /** + * On init + */ + ngOnInit(): void { + // Mark if active + this._markIfActive(this._router.url); + + // Attach a listener to the NavigationEnd event + this._router.events + .pipe( + filter( + (event): event is NavigationEnd => + event instanceof NavigationEnd + ), + takeUntil(this._unsubscribeAll) + ) + .subscribe((event: NavigationEnd) => { + // Mark if active + this._markIfActive(event.urlAfterRedirects); + }); + + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Check if the given item has the given url + * in one of its children + * + * @param item + * @param currentUrl + * @private + */ + private _hasActiveChild( + item: AngorNavigationItem, + currentUrl: string + ): boolean { + const children = item.children; + + if (!children) { + return false; + } + + for (const child of children) { + if (child.children) { + if (this._hasActiveChild(child, currentUrl)) { + return true; + } + } + + // Skip items other than 'basic' + if (child.type !== 'basic') { + continue; + } + + // Check if the child has a link and is active + if ( + child.link && + this._router.isActive(child.link, child.exactMatch || false) + ) { + return true; + } + } + + return false; + } + + /** + * Decide and mark if the item is active + * + * @private + */ + private _markIfActive(currentUrl: string): void { + // Check if the activeItemId is equals to this item id + this.active = this.activeItemId === this.item.id; + + // If the aside has a children that is active, + // always mark it as active + if (this._hasActiveChild(this.item, currentUrl)) { + this.active = true; + } + + // Mark for check + this._changeDetectorRef.markForCheck(); + } +} diff --git a/src/@angor/components/navigation/vertical/components/basic/basic.component.html b/src/@angor/components/navigation/vertical/components/basic/basic.component.html new file mode 100644 index 0000000..7f5d430 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/basic/basic.component.html @@ -0,0 +1,149 @@ + +
+ + @if (item.link && !item.externalLink && !item.function && !item.disabled) { + + + + } + + + @if (item.link && item.externalLink && !item.function && !item.disabled) { + + + + } + + + @if (!item.link && item.function && !item.disabled) { +
+ +
+ } + + + @if (item.link && !item.externalLink && item.function && !item.disabled) { + + + + } + + + @if (item.link && item.externalLink && item.function && !item.disabled) { + + + + } + + + @if (!item.link && !item.function && !item.disabled) { +
+ +
+ } + + + @if (item.disabled) { +
+ +
+ } +
+ + + + + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } +
diff --git a/src/@angor/components/navigation/vertical/components/basic/basic.component.ts b/src/@angor/components/navigation/vertical/components/basic/basic.component.ts new file mode 100644 index 0000000..ff009a1 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/basic/basic.component.ts @@ -0,0 +1,98 @@ +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { + IsActiveMatchOptions, + RouterLink, + RouterLinkActive, +} from '@angular/router'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-basic-item', + templateUrl: './basic.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + RouterLink, + RouterLinkActive, + MatTooltipModule, + NgTemplateOutlet, + MatIconModule, + ], +}) +export class AngorVerticalNavigationBasicItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + // Set the equivalent of {exact: false} as default for active match options. + // We are not assigning the item.isActiveMatchOptions directly to the + // [routerLinkActiveOptions] because if it's "undefined" initially, the router + // will throw an error and stop working. + isActiveMatchOptions: IsActiveMatchOptions = + this._angorUtilsService.subsetMatchOptions; + + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Set the "isActiveMatchOptions" either from item's + // "isActiveMatchOptions" or the equivalent form of + // item's "exactMatch" option + this.isActiveMatchOptions = + this.item.isActiveMatchOptions ?? this.item.exactMatch + ? this._angorUtilsService.exactMatchOptions + : this._angorUtilsService.subsetMatchOptions; + + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.html b/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.html new file mode 100644 index 0000000..74eee6b --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.html @@ -0,0 +1,105 @@ +
+
+ + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } + + + +
+
+ +@if (!isCollapsed) { +
+ @for (item of item.children; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'basic') { + + } + + + @if (item.type === 'collapsable') { + + } + + + @if (item.type === 'divider') { + + } + + + @if (item.type === 'group') { + + } + + + @if (item.type === 'spacer') { + + } + } + } +
+} diff --git a/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.ts b/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.ts new file mode 100644 index 0000000..30cc679 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/collapsable/collapsable.component.ts @@ -0,0 +1,343 @@ +import { BooleanInput } from '@angular/cdk/coercion'; +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + HostBinding, + Input, + OnDestroy, + OnInit, + forwardRef, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { NavigationEnd, Router } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; +import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; +import { AngorVerticalNavigationGroupItemComponent } from '@angor/components/navigation/vertical/components/group/group.component'; +import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { Subject, filter, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-collapsable-item', + templateUrl: './collapsable.component.html', + animations: angorAnimations, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + MatTooltipModule, + MatIconModule, + AngorVerticalNavigationBasicItemComponent, + forwardRef(() => AngorVerticalNavigationCollapsableItemComponent), + AngorVerticalNavigationDividerItemComponent, + AngorVerticalNavigationGroupItemComponent, + AngorVerticalNavigationSpacerItemComponent, + ], +}) +export class AngorVerticalNavigationCollapsableItemComponent + implements OnInit, OnDestroy +{ + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_autoCollapse: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + private _changeDetectorRef = inject(ChangeDetectorRef); + private _router = inject(Router); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() autoCollapse: boolean; + @Input() item: AngorNavigationItem; + @Input() name: string; + + isCollapsed: boolean = true; + isExpanded: boolean = false; + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Host binding for component classes + */ + @HostBinding('class') get classList(): any { + /* eslint-disable @typescript-eslint/naming-convention */ + return { + 'angor-vertical-navigation-item-collapsed': this.isCollapsed, + 'angor-vertical-navigation-item-expanded': this.isExpanded, + }; + /* eslint-enable @typescript-eslint/naming-convention */ + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // If the item has a children that has a matching url with the current url, expand... + if (this._hasActiveChild(this.item, this._router.url)) { + this.expand(); + } + // Otherwise... + else { + // If the autoCollapse is on, collapse... + if (this.autoCollapse) { + this.collapse(); + } + } + + // Listen for the onCollapsableItemCollapsed from the service + this._angorVerticalNavigationComponent.onCollapsableItemCollapsed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((collapsedItem) => { + // Check if the collapsed item is null + if (collapsedItem === null) { + return; + } + + // Collapse if this is a children of the collapsed item + if (this._isChildrenOf(collapsedItem, this.item)) { + this.collapse(); + } + }); + + // Listen for the onCollapsableItemExpanded from the service if the autoCollapse is on + if (this.autoCollapse) { + this._angorVerticalNavigationComponent.onCollapsableItemExpanded + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((expandedItem) => { + // Check if the expanded item is null + if (expandedItem === null) { + return; + } + + // Check if this is a parent of the expanded item + if (this._isChildrenOf(this.item, expandedItem)) { + return; + } + + // Check if this has a children with a matching url with the current active url + if (this._hasActiveChild(this.item, this._router.url)) { + return; + } + + // Check if this is the expanded item + if (this.item === expandedItem) { + return; + } + + // If none of the above conditions are matched, collapse this item + this.collapse(); + }); + } + + // Attach a listener to the NavigationEnd event + this._router.events + .pipe( + filter( + (event): event is NavigationEnd => + event instanceof NavigationEnd + ), + takeUntil(this._unsubscribeAll) + ) + .subscribe((event: NavigationEnd) => { + // If the item has a children that has a matching url with the current url, expand... + if (this._hasActiveChild(this.item, event.urlAfterRedirects)) { + this.expand(); + } + // Otherwise... + else { + // If the autoCollapse is on, collapse... + if (this.autoCollapse) { + this.collapse(); + } + } + }); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Collapse + */ + collapse(): void { + // Return if the item is disabled + if (this.item.disabled) { + return; + } + + // Return if the item is already collapsed + if (this.isCollapsed) { + return; + } + + // Collapse it + this.isCollapsed = true; + this.isExpanded = !this.isCollapsed; + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Execute the observable + this._angorVerticalNavigationComponent.onCollapsableItemCollapsed.next( + this.item + ); + } + + /** + * Expand + */ + expand(): void { + // Return if the item is disabled + if (this.item.disabled) { + return; + } + + // Return if the item is already expanded + if (!this.isCollapsed) { + return; + } + + // Expand it + this.isCollapsed = false; + this.isExpanded = !this.isCollapsed; + + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Execute the observable + this._angorVerticalNavigationComponent.onCollapsableItemExpanded.next( + this.item + ); + } + + /** + * Toggle collapsable + */ + toggleCollapsable(): void { + // Toggle collapse/expand + if (this.isCollapsed) { + this.expand(); + } else { + this.collapse(); + } + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Check if the given item has the given url + * in one of its children + * + * @param item + * @param currentUrl + * @private + */ + private _hasActiveChild( + item: AngorNavigationItem, + currentUrl: string + ): boolean { + const children = item.children; + + if (!children) { + return false; + } + + for (const child of children) { + if (child.children) { + if (this._hasActiveChild(child, currentUrl)) { + return true; + } + } + + // Check if the child has a link and is active + if ( + child.link && + this._router.isActive(child.link, child.exactMatch || false) + ) { + return true; + } + } + + return false; + } + + /** + * Check if this is a children + * of the given item + * + * @param parent + * @param item + * @private + */ + private _isChildrenOf( + parent: AngorNavigationItem, + item: AngorNavigationItem + ): boolean { + const children = parent.children; + + if (!children) { + return false; + } + + if (children.indexOf(item) > -1) { + return true; + } + + for (const child of children) { + if (child.children) { + if (this._isChildrenOf(child, item)) { + return true; + } + } + } + + return false; + } +} diff --git a/src/@angor/components/navigation/vertical/components/divider/divider.component.html b/src/@angor/components/navigation/vertical/components/divider/divider.component.html new file mode 100644 index 0000000..6128222 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/divider/divider.component.html @@ -0,0 +1,5 @@ + +
diff --git a/src/@angor/components/navigation/vertical/components/divider/divider.component.ts b/src/@angor/components/navigation/vertical/components/divider/divider.component.ts new file mode 100644 index 0000000..8591090 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/divider/divider.component.ts @@ -0,0 +1,64 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-divider-item', + templateUrl: './divider.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [NgClass], +}) +export class AngorVerticalNavigationDividerItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/vertical/components/group/group.component.html b/src/@angor/components/navigation/vertical/components/group/group.component.html new file mode 100644 index 0000000..75383c6 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/group/group.component.html @@ -0,0 +1,91 @@ + +
+
+ + @if (item.icon) { + + } + + +
+
+ + {{ item.title }} + +
+ @if (item.subtitle) { +
+ + {{ item.subtitle }} + +
+ } +
+ + + @if (item.badge) { +
+
+ {{ item.badge.title }} +
+
+ } +
+
+ +@for (item of item.children; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'basic') { + + } + + + @if (item.type === 'collapsable') { + + } + + + @if (item.type === 'divider') { + + } + + + @if (item.type === 'group') { + + } + + + @if (item.type === 'spacer') { + + } + } +} diff --git a/src/@angor/components/navigation/vertical/components/group/group.component.ts b/src/@angor/components/navigation/vertical/components/group/group.component.ts new file mode 100644 index 0000000..b71c62d --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/group/group.component.ts @@ -0,0 +1,98 @@ +import { BooleanInput } from '@angular/cdk/coercion'; +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + forwardRef, + inject, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; +import { AngorVerticalNavigationCollapsableItemComponent } from '@angor/components/navigation/vertical/components/collapsable/collapsable.component'; +import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; +import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-group-item', + templateUrl: './group.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + NgClass, + MatIconModule, + AngorVerticalNavigationBasicItemComponent, + AngorVerticalNavigationCollapsableItemComponent, + AngorVerticalNavigationDividerItemComponent, + forwardRef(() => AngorVerticalNavigationGroupItemComponent), + AngorVerticalNavigationSpacerItemComponent, + ], +}) +export class AngorVerticalNavigationGroupItemComponent + implements OnInit, OnDestroy +{ + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_autoCollapse: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() autoCollapse: boolean; + @Input() item: AngorNavigationItem; + @Input() name: string; + + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/@angor/components/navigation/vertical/components/spacer/spacer.component.html b/src/@angor/components/navigation/vertical/components/spacer/spacer.component.html new file mode 100644 index 0000000..3d7125c --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/spacer/spacer.component.html @@ -0,0 +1,5 @@ + +
diff --git a/src/@angor/components/navigation/vertical/components/spacer/spacer.component.ts b/src/@angor/components/navigation/vertical/components/spacer/spacer.component.ts new file mode 100644 index 0000000..b107ff1 --- /dev/null +++ b/src/@angor/components/navigation/vertical/components/spacer/spacer.component.ts @@ -0,0 +1,64 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + inject, + Input, + OnDestroy, + OnInit, +} from '@angular/core'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { AngorNavigationItem } from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationComponent } from '@angor/components/navigation/vertical/vertical.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation-spacer-item', + templateUrl: './spacer.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [NgClass], +}) +export class AngorVerticalNavigationSpacerItemComponent + implements OnInit, OnDestroy +{ + private _changeDetectorRef = inject(ChangeDetectorRef); + private _angorNavigationService = inject(AngorNavigationService); + + @Input() item: AngorNavigationItem; + @Input() name: string; + + private _angorVerticalNavigationComponent: AngorVerticalNavigationComponent; + private _unsubscribeAll: Subject = new Subject(); + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the parent navigation component + this._angorVerticalNavigationComponent = + this._angorNavigationService.getComponent(this.name); + + // Subscribe to onRefreshed on the navigation component + this._angorVerticalNavigationComponent.onRefreshed + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(() => { + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/components/navigation/vertical/styles/appearances/compact.scss b/src/@angor/components/navigation/vertical/styles/appearances/compact.scss new file mode 100644 index 0000000..d1bd8b4 --- /dev/null +++ b/src/@angor/components/navigation/vertical/styles/appearances/compact.scss @@ -0,0 +1,109 @@ +/* Variables */ +:root { + --angor-vertical-navigation-compact-width: 112px; +} + +angor-vertical-navigation { + /* Compact appearance overrides */ + &.angor-vertical-navigation-appearance-compact { + width: var(--angor-vertical-navigation-compact-width); + min-width: var(--angor-vertical-navigation-compact-width); + max-width: var(--angor-vertical-navigation-compact-width); + + /* Left positioned */ + &.angor-vertical-navigation-position-left { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-left: calc( + var(--angor-vertical-navigation-compact-width) * -1 + ); + } + + /* Opened */ + &.angor-vertical-navigation-opened { + margin-left: 0; + } + } + + /* Right positioned */ + &.angor-vertical-navigation-position-right { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-right: calc( + var(--angor-vertical-navigation-compact-width) * -1 + ); + } + + /* Opened */ + &.angor-vertical-navigation-opened { + margin-right: 0; + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + left: auto; + right: var(--angor-vertical-navigation-compact-width); + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + /* Content */ + .angor-vertical-navigation-content { + > angor-vertical-navigation-aside-item, + > angor-vertical-navigation-basic-item { + .angor-vertical-navigation-item-wrapper { + margin: 4px 8px 0 8px; + + .angor-vertical-navigation-item { + flex-direction: column; + justify-content: center; + padding: 12px; + border-radius: 6px; + + .angor-vertical-navigation-item-icon { + margin-right: 0; + } + + .angor-vertical-navigation-item-title-wrapper { + margin-top: 8px; + + .angor-vertical-navigation-item-title { + font-size: 12px; + font-weight: 500; + text-align: center; + line-height: 16px; + } + + .angor-vertical-navigation-item-subtitle { + display: none !important; + } + } + + .angor-vertical-navigation-item-badge { + position: absolute; + top: 12px; + left: 64px; + } + } + } + + > angor-vertical-navigation-collapsable-item { + display: none; + } + + > angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + display: none; + } + } + } + } + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + left: var(--angor-vertical-navigation-compact-width); + } + } +} diff --git a/src/@angor/components/navigation/vertical/styles/appearances/default.scss b/src/@angor/components/navigation/vertical/styles/appearances/default.scss new file mode 100644 index 0000000..06c7069 --- /dev/null +++ b/src/@angor/components/navigation/vertical/styles/appearances/default.scss @@ -0,0 +1,559 @@ +/* Variables */ +:root { + --angor-vertical-navigation-width: 280px; +} + +angor-vertical-navigation { + position: sticky; + display: flex; + flex-direction: column; + flex: 1 0 auto; + top: 0; + width: var(--angor-vertical-navigation-width); + min-width: var(--angor-vertical-navigation-width); + max-width: var(--angor-vertical-navigation-width); + height: 100vh; + min-height: 100vh; + max-height: 100vh; + z-index: 200; + + /* ----------------------------------------------------------------------------------------------------- */ + /* @ Navigation Drawer + /* ----------------------------------------------------------------------------------------------------- */ + + /* Animations */ + &.angor-vertical-navigation-animations-enabled { + transition-duration: 400ms; + transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); + transition-property: visibility, margin-left, margin-right, transform, + width, max-width, min-width; + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + transition-duration: 400ms; + transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); + transition-property: width, max-width, min-width; + } + } + + /* Over mode */ + &.angor-vertical-navigation-mode-over { + position: fixed; + top: 0; + bottom: 0; + } + + /* Left position */ + &.angor-vertical-navigation-position-left { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-left: calc(#{var(--angor-vertical-navigation-width)} * -1); + + &.angor-vertical-navigation-opened { + margin-left: 0; + } + } + + /* Over mode */ + &.angor-vertical-navigation-mode-over { + left: 0; + transform: translate3d(-100%, 0, 0); + + &.angor-vertical-navigation-opened { + transform: translate3d(0, 0, 0); + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + left: 0; + } + } + + /* Right position */ + &.angor-vertical-navigation-position-right { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-right: calc(var(--angor-vertical-navigation-width) * -1); + + &.angor-vertical-navigation-opened { + margin-right: 0; + } + } + + /* Over mode */ + &.angor-vertical-navigation-mode-over { + right: 0; + transform: translate3d(100%, 0, 0); + + &.angor-vertical-navigation-opened { + transform: translate3d(0, 0, 0); + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + right: 0; + } + } + + /* Inner mode */ + &.angor-vertical-navigation-inner { + position: relative; + width: auto; + min-width: 0; + max-width: none; + height: auto; + min-height: 0; + max-height: none; + box-shadow: none; + + .angor-vertical-navigation-wrapper { + position: relative; + overflow: visible; + height: auto; + + .angor-vertical-navigation-content { + overflow: visible !important; + } + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + position: absolute; + display: flex; + flex: 1 1 auto; + flex-direction: column; + top: 0; + bottom: 0; + width: 100%; + height: 100%; + overflow: hidden; + z-index: 10; + background: inherit; + box-shadow: inset -1px 0 0 var(--angor-border); + + /* Header */ + .angor-vertical-navigation-header { + } + + /* Content */ + .angor-vertical-navigation-content { + flex: 1 1 auto; + overflow-x: hidden; + overflow-y: auto; + overscroll-behavior: contain; + + /* Divider */ + > angor-vertical-navigation-divider-item { + margin: 24px 0; + } + + /* Group */ + > angor-vertical-navigation-group-item { + margin-top: 24px; + } + } + + /* Footer */ + .angor-vertical-navigation-footer { + } + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + position: absolute; + display: flex; + flex: 1 1 auto; + flex-direction: column; + top: 0; + bottom: 0; + left: var(--angor-vertical-navigation-width); + width: var(--angor-vertical-navigation-width); + height: 100%; + z-index: 5; + overflow-x: hidden; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + transition-duration: 400ms; + transition-property: left, right; + transition-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1); + background: inherit; + + > angor-vertical-navigation-aside-item { + padding: 24px 0; + + /* First item of the aside */ + > .angor-vertical-navigation-item-wrapper { + display: none !important; + } + } + } + + &.angor-vertical-navigation-position-right { + .angor-vertical-navigation-aside-wrapper { + left: auto; + right: var(--angor-vertical-navigation-width); + } + } + + /* ----------------------------------------------------------------------------------------------------- */ + /* @ Navigation Items + /* ----------------------------------------------------------------------------------------------------- */ + + /* Navigation items common */ + angor-vertical-navigation-aside-item, + angor-vertical-navigation-basic-item, + angor-vertical-navigation-collapsable-item, + angor-vertical-navigation-divider-item, + angor-vertical-navigation-group-item, + angor-vertical-navigation-spacer-item { + display: flex; + flex-direction: column; + flex: 1 0 auto; + user-select: none; + + .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + position: relative; + display: flex; + align-items: center; + justify-content: flex-start; + padding: 10px 16px; + font-size: 13px; + font-weight: 500; + line-height: 20px; + text-decoration: none; + border-radius: 6px; + + /* Disabled state */ + &.angor-vertical-navigation-item-disabled { + cursor: default; + opacity: 0.4; + } + + .angor-vertical-navigation-item-icon { + margin-right: 16px; + } + + .angor-vertical-navigation-item-title-wrapper { + .angor-vertical-navigation-item-subtitle { + font-size: 11px; + line-height: 1.5; + } + } + + .angor-vertical-navigation-item-badge { + margin-left: auto; + + .angor-vertical-navigation-item-badge-content { + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 600; + white-space: nowrap; + height: 20px; + } + } + } + } + } + + /* Aside, Basic, Collapsable, Group */ + angor-vertical-navigation-aside-item, + angor-vertical-navigation-basic-item, + angor-vertical-navigation-collapsable-item, + angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + margin: 0 12px; + } + } + + /* Aside, Basic, Collapsable */ + angor-vertical-navigation-aside-item, + angor-vertical-navigation-basic-item, + angor-vertical-navigation-collapsable-item { + margin-bottom: 4px; + + .angor-vertical-navigation-item { + cursor: pointer; + } + } + + /* Aside */ + angor-vertical-navigation-aside-item { + } + + /* Basic */ + angor-vertical-navigation-basic-item { + } + + /* Collapsable */ + angor-vertical-navigation-collapsable-item { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + .angor-vertical-navigation-item-badge { + + .angor-vertical-navigation-item-arrow { + margin-left: 8px; + } + } + + .angor-vertical-navigation-item-arrow { + height: 20px; + line-height: 20px; + margin-left: auto; + transition: + transform 300ms cubic-bezier(0.25, 0.8, 0.25, 1), + color 375ms cubic-bezier(0.25, 0.8, 0.25, 1); + } + } + } + + &.angor-vertical-navigation-item-expanded { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + .angor-vertical-navigation-item-arrow { + transform: rotate(90deg); + } + } + } + } + + > .angor-vertical-navigation-item-children { + > *:first-child { + margin-top: 6px; + } + + > *:last-child { + padding-bottom: 6px; + + > .angor-vertical-navigation-item-children { + > *:last-child { + padding-bottom: 0; + } + } + } + + .angor-vertical-navigation-item { + padding: 10px 16px; + } + } + + /* 1st level */ + .angor-vertical-navigation-item-children { + overflow: hidden; + + .angor-vertical-navigation-item { + padding-left: 56px; + } + + /* 2nd level */ + .angor-vertical-navigation-item-children { + .angor-vertical-navigation-item { + padding-left: 72px; + } + + /* 3rd level */ + .angor-vertical-navigation-item-children { + .angor-vertical-navigation-item { + padding-left: 88px; + } + + /* 4th level */ + .angor-vertical-navigation-item-children { + .angor-vertical-navigation-item { + padding-left: 104px; + } + } + } + } + } + } + + /* Divider */ + angor-vertical-navigation-divider-item { + margin: 12px 0; + + .angor-vertical-navigation-item-wrapper { + height: 1px; + box-shadow: 0 1px 0 0; + } + } + + /* Group */ + angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + .angor-vertical-navigation-item-badge, + .angor-vertical-navigation-item-icon { + display: none !important; + } + + .angor-vertical-navigation-item-title-wrapper { + .angor-vertical-navigation-item-title { + font-size: 12px; + font-weight: 600; + letter-spacing: 0.05em; + text-transform: uppercase; + } + } + } + } + } + + /* Spacer */ + angor-vertical-navigation-spacer-item { + margin: 6px 0; + } +} + +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Overlay +/* ----------------------------------------------------------------------------------------------------- */ +.angor-vertical-navigation-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 170; + opacity: 0; + background-color: rgba(0, 0, 0, 0.6); + + + .angor-vertical-navigation-aside-overlay { + background-color: transparent; + } +} + +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Aside overlay +/* ----------------------------------------------------------------------------------------------------- */ +.angor-vertical-navigation-aside-overlay { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 169; + opacity: 0; + background-color: rgba(0, 0, 0, 0.3); +} + +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Navigation Items Colors +/* ----------------------------------------------------------------------------------------------------- */ + +/* Navigation items common */ +angor-vertical-navigation-aside-item, +angor-vertical-navigation-basic-item, +angor-vertical-navigation-collapsable-item, +angor-vertical-navigation-group-item { + .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + color: currentColor; + + .angor-vertical-navigation-item-icon { + @apply text-current opacity-60; + } + + .angor-vertical-navigation-item-title-wrapper { + .angor-vertical-navigation-item-title { + @apply text-current opacity-80; + } + + .angor-vertical-navigation-item-subtitle { + @apply text-current opacity-50; + } + } + } + } +} + +/* Aside, Basic, Collapsable */ +angor-vertical-navigation-aside-item, +angor-vertical-navigation-basic-item, +angor-vertical-navigation-collapsable-item { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + /* Active state */ + &:not(.angor-vertical-navigation-item-disabled) { + &.angor-vertical-navigation-item-active, + &.angor-vertical-navigation-item-active-forced { + @apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; + + .angor-vertical-navigation-item-icon { + @apply opacity-100; + } + + .angor-vertical-navigation-item-title { + @apply opacity-100; + } + + .angor-vertical-navigation-item-subtitle { + @apply opacity-100; + } + } + } + + /* Hover state */ + &:not(.angor-vertical-navigation-item-active-forced):not( + .angor-vertical-navigation-item-active + ):not(.angor-vertical-navigation-item-disabled) { + &:hover { + @apply bg-gray-800 bg-opacity-5 dark:bg-white dark:bg-opacity-12; + + .angor-vertical-navigation-item-icon { + @apply opacity-100; + } + + .angor-vertical-navigation-item-title, + .angor-vertical-navigation-item-arrow { + @apply opacity-100; + } + + .angor-vertical-navigation-item-subtitle { + @apply opacity-100; + } + } + } + } + } +} + +/* Collapsable */ +angor-vertical-navigation-collapsable-item { + /* Expanded state */ + &.angor-vertical-navigation-item-expanded { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + .angor-vertical-navigation-item-icon { + @apply opacity-100; + } + + .angor-vertical-navigation-item-title, + .angor-vertical-navigation-item-arrow { + @apply opacity-100; + } + + .angor-vertical-navigation-item-subtitle { + @apply opacity-100; + } + } + } + } +} + +/* Group */ +angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + .angor-vertical-navigation-item-title-wrapper { + .angor-vertical-navigation-item-title { + @apply text-primary-600 opacity-100 dark:text-primary-400; + } + } + } + } +} diff --git a/src/@angor/components/navigation/vertical/styles/appearances/dense.scss b/src/@angor/components/navigation/vertical/styles/appearances/dense.scss new file mode 100644 index 0000000..ded2cf1 --- /dev/null +++ b/src/@angor/components/navigation/vertical/styles/appearances/dense.scss @@ -0,0 +1,196 @@ +/* Variables */ +:root { + --angor-vertical-navigation-width: 280px; + --angor-vertical-navigation-dense-width: 80px; +} + +angor-vertical-navigation { + /* Dense appearance overrides */ + &.angor-vertical-navigation-appearance-dense { + &:not(.angor-vertical-navigation-mode-over) { + width: var(--angor-vertical-navigation-dense-width); + min-width: var(--angor-vertical-navigation-dense-width); + max-width: var(--angor-vertical-navigation-dense-width); + + /* Left positioned */ + &.angor-vertical-navigation-position-left { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-left: calc( + var(--angor-vertical-navigation-dense-width) * -1 + ); + } + + /* Opened */ + &.angor-vertical-navigation-opened { + margin-left: 0; + } + } + + /* Right positioned */ + &.angor-vertical-navigation-position-right { + /* Side mode */ + &.angor-vertical-navigation-mode-side { + margin-right: calc( + var(--angor-vertical-navigation-dense-width) * -1 + ); + } + + /* Opened */ + &.angor-vertical-navigation-opened { + margin-right: 0; + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + left: auto; + right: var(--angor-vertical-navigation-dense-width); + } + + &.angor-vertical-navigation-hover { + .angor-vertical-navigation-aside-wrapper { + left: auto; + right: var(--angor-vertical-navigation-width); + } + } + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + /* Content */ + .angor-vertical-navigation-content { + angor-vertical-navigation-aside-item, + angor-vertical-navigation-basic-item, + angor-vertical-navigation-collapsable-item, + angor-vertical-navigation-group-item { + .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + width: calc( + var(--angor-vertical-navigation-dense-width) - + 24px + ); + min-width: calc( + var(--angor-vertical-navigation-dense-width) - + 24px + ); + max-width: calc( + var(--angor-vertical-navigation-dense-width) - + 24px + ); + + .angor-vertical-navigation-item-arrow, + .angor-vertical-navigation-item-badge, + .angor-vertical-navigation-item-title-wrapper { + transition: opacity 400ms + cubic-bezier(0.25, 0.8, 0.25, 1); + } + } + } + } + + angor-vertical-navigation-group-item { + &:first-of-type { + margin-top: 0; + } + } + } + } + + &:not(.angor-vertical-navigation-hover):not( + .angor-vertical-navigation-mode-over + ) { + /* Wrapper */ + .angor-vertical-navigation-wrapper { + /* Content */ + .angor-vertical-navigation-content { + .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + padding: 10px 16px; + + .angor-vertical-navigation-item-arrow, + .angor-vertical-navigation-item-badge, + .angor-vertical-navigation-item-title-wrapper { + white-space: nowrap; + opacity: 0; + } + } + } + + angor-vertical-navigation-collapsable-item { + .angor-vertical-navigation-item-children { + display: none; + } + } + + angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + &:before { + content: ''; + position: absolute; + top: 20px; + width: 23px; + border-top-width: 2px; + } + } + } + } + } + } + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + left: var(--angor-vertical-navigation-dense-width); + } + + /* Hover */ + &.angor-vertical-navigation-hover { + .angor-vertical-navigation-wrapper { + width: var(--angor-vertical-navigation-width); + + .angor-vertical-navigation-content { + .angor-vertical-navigation-item-wrapper { + .angor-vertical-navigation-item { + width: calc( + var(--angor-vertical-navigation-width) - 24px + ); + min-width: calc( + var(--angor-vertical-navigation-width) - 24px + ); + max-width: calc( + var(--angor-vertical-navigation-width) - 24px + ); + + .angor-vertical-navigation-item-arrow, + .angor-vertical-navigation-item-badge, + .angor-vertical-navigation-item-title-wrapper { + white-space: nowrap; + animation: removeWhiteSpaceNoWrap 1ms linear + 350ms; + animation-fill-mode: forwards; + } + } + } + } + } + + .angor-vertical-navigation-aside-wrapper { + left: var(--angor-vertical-navigation-width); + } + } + } +} + +@keyframes removeWhiteSpaceNoWrap { + 0% { + white-space: nowrap; + } + 99% { + white-space: nowrap; + } + 100% { + white-space: normal; + } +} diff --git a/src/@angor/components/navigation/vertical/styles/appearances/thin.scss b/src/@angor/components/navigation/vertical/styles/appearances/thin.scss new file mode 100644 index 0000000..69b0c27 --- /dev/null +++ b/src/@angor/components/navigation/vertical/styles/appearances/thin.scss @@ -0,0 +1,97 @@ +/* Variables */ +:root { + --angor-vertical-navigation-thin-width: 80px; +} + +angor-vertical-navigation { + /* Thin appearance overrides */ + &.angor-vertical-navigation-appearance-thin { + width: var(--angor-vertical-navigation-thin-width); + min-width: var(--angor-vertical-navigation-thin-width); + max-width: var(--angor-vertical-navigation-thin-width); + + /* Left positioned */ + &.angor-vertical-navigation-position-left { + &.angor-vertical-navigation-mode-side { + margin-left: calc( + var(--angor-vertical-navigation-thin-width) * -1 + ); + } + + &.angor-vertical-navigation-opened { + margin-left: 0; + } + } + + /* Right positioned */ + &.angor-vertical-navigation-position-right { + &.angor-vertical-navigation-mode-side { + margin-right: calc( + var(--angor-vertical-navigation-thin-width) * -1 + ); + } + + &.angor-vertical-navigation-opened { + margin-right: 0; + } + + .angor-vertical-navigation-aside-wrapper { + left: auto; + right: var(--angor-vertical-navigation-thin-width); + } + } + + /* Wrapper */ + .angor-vertical-navigation-wrapper { + /* Content */ + .angor-vertical-navigation-content { + > angor-vertical-navigation-aside-item, + > angor-vertical-navigation-basic-item { + flex-direction: column; + justify-content: center; + height: 64px; + min-height: 64px; + max-height: 64px; + padding: 0 16px; + + .angor-vertical-navigation-item-wrapper { + display: flex; + align-items: center; + justify-content: center; + + .angor-vertical-navigation-item { + justify-content: center; + padding: 12px; + border-radius: 4px; + + .angor-vertical-navigation-item-icon { + margin: 0; + } + + .angor-vertical-navigation-item-arrow, + .angor-vertical-navigation-item-badge-content, + .angor-vertical-navigation-item-title-wrapper { + display: none; + } + } + } + } + + > angor-vertical-navigation-collapsable-item { + display: none; + } + + > angor-vertical-navigation-group-item { + > .angor-vertical-navigation-item-wrapper { + display: none; + } + } + } + } + + /* Aside wrapper */ + .angor-vertical-navigation-aside-wrapper { + left: var(--angor-vertical-navigation-thin-width); + } + } +} diff --git a/src/@angor/components/navigation/vertical/vertical.component.html b/src/@angor/components/navigation/vertical/vertical.component.html new file mode 100644 index 0000000..ba31776 --- /dev/null +++ b/src/@angor/components/navigation/vertical/vertical.component.html @@ -0,0 +1,122 @@ +
+ +
+ +
+ + +
+ +
+ +
+ + + @for (item of navigation; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'aside') { + + } + + + @if (item.type === 'basic') { + + } + + + @if (item.type === 'collapsable') { + + } + + + @if (item.type === 'divider') { + + } + + + @if (item.type === 'group') { + + } + + + @if (item.type === 'spacer') { + + } + } + } + + +
+ + + +
+ + +@if (activeAsideItemId) { +
+ + @for (item of navigation; track trackByFn($index, item)) { + + @if ((item.hidden && !item.hidden(item)) || !item.hidden) { + + @if (item.type === 'aside' && item.id === activeAsideItemId) { + + } + } + } +
+} diff --git a/src/@angor/components/navigation/vertical/vertical.component.scss b/src/@angor/components/navigation/vertical/vertical.component.scss new file mode 100644 index 0000000..ea2db44 --- /dev/null +++ b/src/@angor/components/navigation/vertical/vertical.component.scss @@ -0,0 +1,4 @@ +@use 'styles/appearances/default'; +@use 'styles/appearances/compact'; +@use 'styles/appearances/dense'; +@use 'styles/appearances/thin'; diff --git a/src/@angor/components/navigation/vertical/vertical.component.ts b/src/@angor/components/navigation/vertical/vertical.component.ts new file mode 100644 index 0000000..47e06fe --- /dev/null +++ b/src/@angor/components/navigation/vertical/vertical.component.ts @@ -0,0 +1,831 @@ +import { + animate, + AnimationBuilder, + AnimationPlayer, + style, +} from '@angular/animations'; +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { ScrollStrategy, ScrollStrategyOptions } from '@angular/cdk/overlay'; +import { DOCUMENT } from '@angular/common'; +import { + AfterViewInit, + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + EventEmitter, + HostBinding, + HostListener, + inject, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + QueryList, + Renderer2, + SimpleChanges, + ViewChild, + ViewChildren, + ViewEncapsulation, +} from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorNavigationService } from '@angor/components/navigation/navigation.service'; +import { + AngorNavigationItem, + AngorVerticalNavigationAppearance, + AngorVerticalNavigationMode, + AngorVerticalNavigationPosition, +} from '@angor/components/navigation/navigation.types'; +import { AngorVerticalNavigationAsideItemComponent } from '@angor/components/navigation/vertical/components/aside/aside.component'; +import { AngorVerticalNavigationBasicItemComponent } from '@angor/components/navigation/vertical/components/basic/basic.component'; +import { AngorVerticalNavigationCollapsableItemComponent } from '@angor/components/navigation/vertical/components/collapsable/collapsable.component'; +import { AngorVerticalNavigationDividerItemComponent } from '@angor/components/navigation/vertical/components/divider/divider.component'; +import { AngorVerticalNavigationGroupItemComponent } from '@angor/components/navigation/vertical/components/group/group.component'; +import { AngorVerticalNavigationSpacerItemComponent } from '@angor/components/navigation/vertical/components/spacer/spacer.component'; +import { AngorScrollbarDirective } from '@angor/directives/scrollbar/scrollbar.directive'; +import { AngorUtilsService } from '@angor/services/utils/utils.service'; +import { + delay, + filter, + merge, + ReplaySubject, + Subject, + Subscription, + takeUntil, +} from 'rxjs'; + +@Component({ + selector: 'angor-vertical-navigation', + templateUrl: './vertical.component.html', + styleUrls: ['./vertical.component.scss'], + animations: angorAnimations, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'angorVerticalNavigation', + standalone: true, + imports: [ + AngorScrollbarDirective, + AngorVerticalNavigationAsideItemComponent, + AngorVerticalNavigationBasicItemComponent, + AngorVerticalNavigationCollapsableItemComponent, + AngorVerticalNavigationDividerItemComponent, + AngorVerticalNavigationGroupItemComponent, + AngorVerticalNavigationSpacerItemComponent, + ], +}) +export class AngorVerticalNavigationComponent + implements OnChanges, OnInit, AfterViewInit, OnDestroy +{ + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_inner: BooleanInput; + static ngAcceptInputType_opened: BooleanInput; + static ngAcceptInputType_transparentOverlay: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + private _animationBuilder = inject(AnimationBuilder); + private _changeDetectorRef = inject(ChangeDetectorRef); + private _document = inject(DOCUMENT); + private _elementRef = inject(ElementRef); + private _renderer2 = inject(Renderer2); + private _router = inject(Router); + private _scrollStrategyOptions = inject(ScrollStrategyOptions); + private _angorNavigationService = inject(AngorNavigationService); + private _angorUtilsService = inject(AngorUtilsService); + + @Input() appearance: AngorVerticalNavigationAppearance = 'default'; + @Input() autoCollapse: boolean = true; + @Input() inner: boolean = false; + @Input() mode: AngorVerticalNavigationMode = 'side'; + @Input() name: string = this._angorUtilsService.randomId(); + @Input() navigation: AngorNavigationItem[]; + @Input() opened: boolean = true; + @Input() position: AngorVerticalNavigationPosition = 'left'; + @Input() transparentOverlay: boolean = false; + @Output() + readonly appearanceChanged: EventEmitter = + new EventEmitter(); + @Output() readonly modeChanged: EventEmitter = + new EventEmitter(); + @Output() readonly openedChanged: EventEmitter = + new EventEmitter(); + @Output() + readonly positionChanged: EventEmitter = + new EventEmitter(); + @ViewChild('navigationContent') private _navigationContentEl: ElementRef; + + activeAsideItemId: string | null = null; + onCollapsableItemCollapsed: ReplaySubject = + new ReplaySubject(1); + onCollapsableItemExpanded: ReplaySubject = + new ReplaySubject(1); + onRefreshed: ReplaySubject = new ReplaySubject(1); + private _animationsEnabled: boolean = false; + private _asideOverlay: HTMLElement; + private readonly _handleAsideOverlayClick: any; + private readonly _handleOverlayClick: any; + private _hovered: boolean = false; + private _mutationObserver: MutationObserver; + private _overlay: HTMLElement; + private _player: AnimationPlayer; + private _scrollStrategy: ScrollStrategy = + this._scrollStrategyOptions.block(); + private _angorScrollbarDirectives!: QueryList; + private _angorScrollbarDirectivesSubscription: Subscription; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor() { + this._handleAsideOverlayClick = (): void => { + this.closeAside(); + }; + this._handleOverlayClick = (): void => { + this.close(); + }; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Host binding for component classes + */ + @HostBinding('class') get classList(): any { + /* eslint-disable @typescript-eslint/naming-convention */ + return { + 'angor-vertical-navigation-animations-enabled': + this._animationsEnabled, + [`angor-vertical-navigation-appearance-${this.appearance}`]: true, + 'angor-vertical-navigation-hover': this._hovered, + 'angor-vertical-navigation-inner': this.inner, + 'angor-vertical-navigation-mode-over': this.mode === 'over', + 'angor-vertical-navigation-mode-side': this.mode === 'side', + 'angor-vertical-navigation-opened': this.opened, + 'angor-vertical-navigation-position-left': this.position === 'left', + 'angor-vertical-navigation-position-right': + this.position === 'right', + }; + /* eslint-enable @typescript-eslint/naming-convention */ + } + + /** + * Host binding for component inline styles + */ + @HostBinding('style') get styleList(): any { + return { + visibility: this.opened ? 'visible' : 'hidden', + }; + } + + /** + * Setter for angorScrollbarDirectives + */ + @ViewChildren(AngorScrollbarDirective) + set angorScrollbarDirectives( + angorScrollbarDirectives: QueryList + ) { + // Store the directives + this._angorScrollbarDirectives = angorScrollbarDirectives; + + // Return if there are no directives + if (angorScrollbarDirectives.length === 0) { + return; + } + + // Unsubscribe the previous subscriptions + if (this._angorScrollbarDirectivesSubscription) { + this._angorScrollbarDirectivesSubscription.unsubscribe(); + } + + // Update the scrollbars on collapsable items' collapse/expand + this._angorScrollbarDirectivesSubscription = merge( + this.onCollapsableItemCollapsed, + this.onCollapsableItemExpanded + ) + .pipe(takeUntil(this._unsubscribeAll), delay(250)) + .subscribe(() => { + // Loop through the scrollbars and update them + angorScrollbarDirectives.forEach((angorScrollbarDirective) => { + angorScrollbarDirective.update(); + }); + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Decorated methods + // ----------------------------------------------------------------------------------------------------- + + /** + * On mouseenter + * + * @private + */ + @HostListener('mouseenter') + private _onMouseenter(): void { + // Enable the animations + this._enableAnimations(); + + // Set the hovered + this._hovered = true; + } + + /** + * On mouseleave + * + * @private + */ + @HostListener('mouseleave') + private _onMouseleave(): void { + // Enable the animations + this._enableAnimations(); + + // Set the hovered + this._hovered = false; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On changes + * + * @param changes + */ + ngOnChanges(changes: SimpleChanges): void { + // Appearance + if ('appearance' in changes) { + // Execute the observable + this.appearanceChanged.next(changes.appearance.currentValue); + } + + // Inner + if ('inner' in changes) { + // Coerce the value to a boolean + this.inner = coerceBooleanProperty(changes.inner.currentValue); + } + + // Mode + if ('mode' in changes) { + // Get the previous and current values + const currentMode = changes.mode.currentValue; + const previousMode = changes.mode.previousValue; + + // Disable the animations + this._disableAnimations(); + + // If the mode changes: 'over -> side' + if (previousMode === 'over' && currentMode === 'side') { + // Hide the overlay + this._hideOverlay(); + } + + // If the mode changes: 'side -> over' + if (previousMode === 'side' && currentMode === 'over') { + // Close the aside + this.closeAside(); + + // If the navigation is opened + if (this.opened) { + // Show the overlay + this._showOverlay(); + } + } + + // Execute the observable + this.modeChanged.next(currentMode); + + // Enable the animations after a delay + // The delay must be bigger than the current transition-duration + // to make sure nothing will be animated while the mode changing + setTimeout(() => { + this._enableAnimations(); + }, 500); + } + + // Navigation + if ('navigation' in changes) { + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + // Opened + if ('opened' in changes) { + // Coerce the value to a boolean + this.opened = coerceBooleanProperty(changes.opened.currentValue); + + // Open/close the navigation + this._toggleOpened(this.opened); + } + + // Position + if ('position' in changes) { + // Execute the observable + this.positionChanged.next(changes.position.currentValue); + } + + // Transparent overlay + if ('transparentOverlay' in changes) { + // Coerce the value to a boolean + this.transparentOverlay = coerceBooleanProperty( + changes.transparentOverlay.currentValue + ); + } + } + + /** + * On init + */ + ngOnInit(): void { + // Make sure the name input is not an empty string + if (this.name === '') { + this.name = this._angorUtilsService.randomId(); + } + + // Register the navigation component + this._angorNavigationService.registerComponent(this.name, this); + + // Subscribe to the 'NavigationEnd' event + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + // If the mode is 'over' and the navigation is opened... + if (this.mode === 'over' && this.opened) { + // Close the navigation + this.close(); + } + + // If the mode is 'side' and the aside is active... + if (this.mode === 'side' && this.activeAsideItemId) { + // Close the aside + this.closeAside(); + } + }); + } + + /** + * After view init + */ + ngAfterViewInit(): void { + // Fix for Firefox. + // + // Because 'position: sticky' doesn't work correctly inside a 'position: fixed' parent, + // adding the '.cdk-global-scrollblock' to the html element breaks the navigation's position. + // This fixes the problem by reading the 'top' value from the html element and adding it as a + // 'marginTop' to the navigation itself. + this._mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + const mutationTarget = mutation.target as HTMLElement; + if (mutation.attributeName === 'class') { + if ( + mutationTarget.classList.contains( + 'cdk-global-scrollblock' + ) + ) { + const top = parseInt(mutationTarget.style.top, 10); + this._renderer2.setStyle( + this._elementRef.nativeElement, + 'margin-top', + `${Math.abs(top)}px` + ); + } else { + this._renderer2.setStyle( + this._elementRef.nativeElement, + 'margin-top', + null + ); + } + } + }); + }); + this._mutationObserver.observe(this._document.documentElement, { + attributes: true, + attributeFilter: ['class'], + }); + + setTimeout(() => { + // Return if 'navigation content' element does not exist + if (!this._navigationContentEl) { + return; + } + + // If 'navigation content' element doesn't have + // perfect scrollbar activated on it... + if ( + !this._navigationContentEl.nativeElement.classList.contains( + 'ps' + ) + ) { + // Find the active item + const activeItem = + this._navigationContentEl.nativeElement.querySelector( + '.angor-vertical-navigation-item-active' + ); + + // If the active item exists, scroll it into view + if (activeItem) { + activeItem.scrollIntoView(); + } + } + // Otherwise + else { + // Go through all the scrollbar directives + this._angorScrollbarDirectives.forEach( + (angorScrollbarDirective) => { + // Skip if not enabled + if (!angorScrollbarDirective.isEnabled()) { + return; + } + + // Scroll to the active element + angorScrollbarDirective.scrollToElement( + '.angor-vertical-navigation-item-active', + -120, + true + ); + } + ); + } + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Disconnect the mutation observer + this._mutationObserver.disconnect(); + + // Forcefully close the navigation and aside in case they are opened + this.close(); + this.closeAside(); + + // Deregister the navigation component from the registry + this._angorNavigationService.deregisterComponent(this.name); + + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Refresh the component to apply the changes + */ + refresh(): void { + // Mark for check + this._changeDetectorRef.markForCheck(); + + // Execute the observable + this.onRefreshed.next(true); + } + + /** + * Open the navigation + */ + open(): void { + // Return if the navigation is already open + if (this.opened) { + return; + } + + // Set the opened + this._toggleOpened(true); + } + + /** + * Close the navigation + */ + close(): void { + // Return if the navigation is already closed + if (!this.opened) { + return; + } + + // Close the aside + this.closeAside(); + + // Set the opened + this._toggleOpened(false); + } + + /** + * Toggle the navigation + */ + toggle(): void { + // Toggle + if (this.opened) { + this.close(); + } else { + this.open(); + } + } + + /** + * Open the aside + * + * @param item + */ + openAside(item: AngorNavigationItem): void { + // Return if the item is disabled + if (item.disabled || !item.id) { + return; + } + + // Open + this.activeAsideItemId = item.id; + + // Show the aside overlay + this._showAsideOverlay(); + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Close the aside + */ + closeAside(): void { + // Close + this.activeAsideItemId = null; + + // Hide the aside overlay + this._hideAsideOverlay(); + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Toggle the aside + * + * @param item + */ + toggleAside(item: AngorNavigationItem): void { + // Toggle + if (this.activeAsideItemId === item.id) { + this.closeAside(); + } else { + this.openAside(item); + } + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Enable the animations + * + * @private + */ + private _enableAnimations(): void { + // Return if the animations are already enabled + if (this._animationsEnabled) { + return; + } + + // Enable the animations + this._animationsEnabled = true; + } + + /** + * Disable the animations + * + * @private + */ + private _disableAnimations(): void { + // Return if the animations are already disabled + if (!this._animationsEnabled) { + return; + } + + // Disable the animations + this._animationsEnabled = false; + } + + /** + * Show the overlay + * + * @private + */ + private _showOverlay(): void { + // Return if there is already an overlay + if (this._asideOverlay) { + return; + } + + // Create the overlay element + this._overlay = this._renderer2.createElement('div'); + + // Add a class to the overlay element + this._overlay.classList.add('angor-vertical-navigation-overlay'); + + // Add a class depending on the transparentOverlay option + if (this.transparentOverlay) { + this._overlay.classList.add( + 'angor-vertical-navigation-overlay-transparent' + ); + } + + // Append the overlay to the parent of the navigation + this._renderer2.appendChild( + this._elementRef.nativeElement.parentElement, + this._overlay + ); + + // Enable block scroll strategy + this._scrollStrategy.enable(); + + // Create the enter animation and attach it to the player + this._player = this._animationBuilder + .build([ + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 1 }) + ), + ]) + .create(this._overlay); + + // Play the animation + this._player.play(); + + // Add an event listener to the overlay + this._overlay.addEventListener('click', this._handleOverlayClick); + } + + /** + * Hide the overlay + * + * @private + */ + private _hideOverlay(): void { + if (!this._overlay) { + return; + } + + // Create the leave animation and attach it to the player + this._player = this._animationBuilder + .build([ + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 0 }) + ), + ]) + .create(this._overlay); + + // Play the animation + this._player.play(); + + // Once the animation is done... + this._player.onDone(() => { + // If the overlay still exists... + if (this._overlay) { + // Remove the event listener + this._overlay.removeEventListener( + 'click', + this._handleOverlayClick + ); + + // Remove the overlay + this._overlay.parentNode.removeChild(this._overlay); + this._overlay = null; + } + + // Disable block scroll strategy + this._scrollStrategy.disable(); + }); + } + + /** + * Show the aside overlay + * + * @private + */ + private _showAsideOverlay(): void { + // Return if there is already an overlay + if (this._asideOverlay) { + return; + } + + // Create the aside overlay element + this._asideOverlay = this._renderer2.createElement('div'); + + // Add a class to the aside overlay element + this._asideOverlay.classList.add( + 'angor-vertical-navigation-aside-overlay' + ); + + // Append the aside overlay to the parent of the navigation + this._renderer2.appendChild( + this._elementRef.nativeElement.parentElement, + this._asideOverlay + ); + + // Create the enter animation and attach it to the player + this._player = this._animationBuilder + .build([ + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 1 }) + ), + ]) + .create(this._asideOverlay); + + // Play the animation + this._player.play(); + + // Add an event listener to the aside overlay + this._asideOverlay.addEventListener( + 'click', + this._handleAsideOverlayClick + ); + } + + /** + * Hide the aside overlay + * + * @private + */ + private _hideAsideOverlay(): void { + if (!this._asideOverlay) { + return; + } + + // Create the leave animation and attach it to the player + this._player = this._animationBuilder + .build([ + animate( + '300ms cubic-bezier(0.25, 0.8, 0.25, 1)', + style({ opacity: 0 }) + ), + ]) + .create(this._asideOverlay); + + // Play the animation + this._player.play(); + + // Once the animation is done... + this._player.onDone(() => { + // If the aside overlay still exists... + if (this._asideOverlay) { + // Remove the event listener + this._asideOverlay.removeEventListener( + 'click', + this._handleAsideOverlayClick + ); + + // Remove the aside overlay + this._asideOverlay.parentNode.removeChild(this._asideOverlay); + this._asideOverlay = null; + } + }); + } + + /** + * Open/close the navigation + * + * @param open + * @private + */ + private _toggleOpened(open: boolean): void { + // Set the opened + this.opened = open; + + // Enable the animations + this._enableAnimations(); + + // If the navigation opened, and the mode + // is 'over', show the overlay + if (this.mode === 'over') { + if (this.opened) { + this._showOverlay(); + } else { + this._hideOverlay(); + } + } + + // Execute the observable + this.openedChanged.next(open); + } +} diff --git a/src/@angor/directives/scroll-reset/index.ts b/src/@angor/directives/scroll-reset/index.ts new file mode 100644 index 0000000..a2e08e9 --- /dev/null +++ b/src/@angor/directives/scroll-reset/index.ts @@ -0,0 +1 @@ +export * from '@angor/directives/scroll-reset/public-api'; diff --git a/src/@angor/directives/scroll-reset/public-api.ts b/src/@angor/directives/scroll-reset/public-api.ts new file mode 100644 index 0000000..2894418 --- /dev/null +++ b/src/@angor/directives/scroll-reset/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/directives/scroll-reset/scroll-reset.directive'; diff --git a/src/@angor/directives/scroll-reset/scroll-reset.directive.ts b/src/@angor/directives/scroll-reset/scroll-reset.directive.ts new file mode 100644 index 0000000..ba91485 --- /dev/null +++ b/src/@angor/directives/scroll-reset/scroll-reset.directive.ts @@ -0,0 +1,36 @@ +import { + Directive, + ElementRef, + inject, + OnDestroy, + OnInit, +} from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { filter, Subject, takeUntil } from 'rxjs'; + +@Directive({ + selector: '[angorScrollReset]', + exportAs: 'angorScrollReset', + standalone: true, +}) +export class AngorScrollResetDirective implements OnInit, OnDestroy { + private _elementRef = inject(ElementRef); + private _router = inject(Router); + private _unsubscribeAll = new Subject(); + + ngOnInit(): void { + this._router.events + .pipe( + filter(event => event instanceof NavigationEnd), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + this._elementRef.nativeElement.scrollTop = 0; + }); + } + + ngOnDestroy(): void { + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } +} diff --git a/src/@angor/directives/scrollbar/index.ts b/src/@angor/directives/scrollbar/index.ts new file mode 100644 index 0000000..a25a903 --- /dev/null +++ b/src/@angor/directives/scrollbar/index.ts @@ -0,0 +1 @@ +export * from '@angor/directives/scrollbar/public-api'; diff --git a/src/@angor/directives/scrollbar/public-api.ts b/src/@angor/directives/scrollbar/public-api.ts new file mode 100644 index 0000000..3040640 --- /dev/null +++ b/src/@angor/directives/scrollbar/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/directives/scrollbar/scrollbar.directive'; diff --git a/src/@angor/directives/scrollbar/scrollbar.directive.ts b/src/@angor/directives/scrollbar/scrollbar.directive.ts new file mode 100644 index 0000000..4792f83 --- /dev/null +++ b/src/@angor/directives/scrollbar/scrollbar.directive.ts @@ -0,0 +1,232 @@ +import { BooleanInput, coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Platform } from '@angular/cdk/platform'; +import { + Directive, + ElementRef, + Input, + OnChanges, + OnDestroy, + OnInit, + SimpleChanges, + inject, +} from '@angular/core'; +import { + ScrollbarGeometry, + ScrollbarPosition, +} from '@angor/directives/scrollbar/scrollbar.types'; +import { merge } from 'lodash-es'; +import PerfectScrollbar from 'perfect-scrollbar'; +import { Subject, debounceTime, fromEvent, takeUntil } from 'rxjs'; + +@Directive({ + selector: '[angorScrollbar]', + exportAs: 'angorScrollbar', + standalone: true, +}) +export class AngorScrollbarDirective implements OnChanges, OnInit, OnDestroy { + static ngAcceptInputType_angorScrollbar: BooleanInput; + + private _elementRef = inject(ElementRef); + private _platform = inject(Platform); + + @Input() angorScrollbar: boolean = true; + @Input() angorScrollbarOptions: PerfectScrollbar.Options; + + private _animation: number; + private _options: PerfectScrollbar.Options; + private _ps: PerfectScrollbar | null = null; + private _unsubscribeAll: Subject = new Subject(); + + get elementRef(): ElementRef { + return this._elementRef; + } + + get ps(): PerfectScrollbar | null { + return this._ps; + } + + ngOnChanges(changes: SimpleChanges): void { + if ('angorScrollbar' in changes) { + this.angorScrollbar = coerceBooleanProperty(changes.angorScrollbar.currentValue); + this.angorScrollbar ? this._initScrollbar() : this._destroyScrollbar(); + } + + if ('angorScrollbarOptions' in changes) { + this._options = merge({}, this._options, changes.angorScrollbarOptions.currentValue); + this._reinitializeScrollbar(); + } + } + + ngOnInit(): void { + fromEvent(window, 'resize') + .pipe(takeUntil(this._unsubscribeAll), debounceTime(150)) + .subscribe(() => this.update()); + } + + ngOnDestroy(): void { + this._destroyScrollbar(); + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + } + + isEnabled(): boolean { + return this.angorScrollbar; + } + + update(): void { + this._ps?.update(); + } + + destroy(): void { + this.ngOnDestroy(); + } + + geometry(prefix: string = 'scroll'): ScrollbarGeometry { + return new ScrollbarGeometry( + this._elementRef.nativeElement[`${prefix}Left`], + this._elementRef.nativeElement[`${prefix}Top`], + this._elementRef.nativeElement[`${prefix}Width`], + this._elementRef.nativeElement[`${prefix}Height`] + ); + } + + position(absolute: boolean = false): ScrollbarPosition { + if (!absolute && this._ps) { + return new ScrollbarPosition(this._ps.reach.x || 0, this._ps.reach.y || 0); + } else { + return new ScrollbarPosition( + this._elementRef.nativeElement.scrollLeft, + this._elementRef.nativeElement.scrollTop + ); + } + } + + scrollTo(x: number, y?: number, speed?: number): void { + if (y == null && speed == null) { + this.animateScrolling('scrollTop', x, speed); + } else { + if (x != null) { + this.scrollToX(x, speed); + } + + if (y != null) { + this.scrollToY(y, speed); + } + } + } + + + scrollToX(x: number, speed?: number): void { + this.animateScrolling('scrollLeft', x, speed); + } + + scrollToY(y: number, speed?: number): void { + this.animateScrolling('scrollTop', y, speed); + } + + scrollToTop(offset: number = 0, speed?: number): void { + this.animateScrolling('scrollTop', offset, speed); + } + + scrollToBottom(offset: number = 0, speed?: number): void { + const top = this._elementRef.nativeElement.scrollHeight - this._elementRef.nativeElement.clientHeight; + this.animateScrolling('scrollTop', top - offset, speed); + } + + scrollToLeft(offset: number = 0, speed?: number): void { + this.animateScrolling('scrollLeft', offset, speed); + } + + scrollToRight(offset: number = 0, speed?: number): void { + const left = this._elementRef.nativeElement.scrollWidth - this._elementRef.nativeElement.clientWidth; + this.animateScrolling('scrollLeft', left - offset, speed); + } + + scrollToElement( + selector: string, + offset: number = 0, + ignoreVisible: boolean = false, + speed?: number + ): void { + const element = this._elementRef.nativeElement.querySelector(selector); + if (!element) return; + + const elementPos = element.getBoundingClientRect(); + const scrollerPos = this._elementRef.nativeElement.getBoundingClientRect(); + + if (this._elementRef.nativeElement.classList.contains('ps--active-x')) { + this._scrollToInAxis(elementPos.left, scrollerPos.left, 'scrollLeft', offset, ignoreVisible, speed); + } + + if (this._elementRef.nativeElement.classList.contains('ps--active-y')) { + this._scrollToInAxis(elementPos.top, scrollerPos.top, 'scrollTop', offset, ignoreVisible, speed); + } + } + + animateScrolling(target: string, value: number, speed?: number): void { + if (this._animation) { + window.cancelAnimationFrame(this._animation); + } + + if (!speed || typeof window === 'undefined') { + this._elementRef.nativeElement[target] = value; + } else if (value !== this._elementRef.nativeElement[target]) { + this._smoothScroll(target, value, speed); + } + } + + private _initScrollbar(): void { + if (this._ps || this._platform.ANDROID || this._platform.IOS || !this._platform.isBrowser) return; + this._ps = new PerfectScrollbar(this._elementRef.nativeElement, { ...this._options }); + } + + private _destroyScrollbar(): void { + this._ps?.destroy(); + this._ps = null; + } + + private _reinitializeScrollbar(): void { + setTimeout(() => this._destroyScrollbar()); + setTimeout(() => this._initScrollbar()); + } + + private _scrollToInAxis( + elementPos: number, + scrollerPos: number, + target: string, + offset: number, + ignoreVisible: boolean, + speed?: number + ): void { + if (ignoreVisible && elementPos <= scrollerPos - Math.abs(offset)) return; + + const currentPos = this._elementRef.nativeElement[target]; + const position = elementPos - scrollerPos + currentPos; + this.animateScrolling(target, position + offset, speed); + } + + private _smoothScroll(target: string, value: number, speed: number): void { + let scrollCount = 0; + let oldValue = this._elementRef.nativeElement[target]; + const cosParameter = (oldValue - value) / 2; + let oldTimestamp = performance.now(); + + const step = (newTimestamp: number) => { + scrollCount += Math.PI / (speed / (newTimestamp - oldTimestamp)); + const newValue = Math.round(value + cosParameter + cosParameter * Math.cos(scrollCount)); + + if (this._elementRef.nativeElement[target] === oldValue) { + if (scrollCount >= Math.PI) { + this.animateScrolling(target, value, 0); + } else { + this._elementRef.nativeElement[target] = newValue; + oldValue = this._elementRef.nativeElement[target]; + oldTimestamp = newTimestamp; + this._animation = window.requestAnimationFrame(step); + } + } + }; + + window.requestAnimationFrame(step); + } +} diff --git a/src/@angor/directives/scrollbar/scrollbar.types.ts b/src/@angor/directives/scrollbar/scrollbar.types.ts new file mode 100644 index 0000000..48a5280 --- /dev/null +++ b/src/@angor/directives/scrollbar/scrollbar.types.ts @@ -0,0 +1,15 @@ +export class ScrollbarGeometry { + constructor( + public x: number, + public y: number, + public w: number, + public h: number + ) {} +} + +export class ScrollbarPosition { + constructor( + public x: number | 'start' | 'end', + public y: number | 'start' | 'end' + ) {} +} diff --git a/src/@angor/index.ts b/src/@angor/index.ts new file mode 100644 index 0000000..6d425b5 --- /dev/null +++ b/src/@angor/index.ts @@ -0,0 +1 @@ +export * from './angor.provider'; diff --git a/src/@angor/lib/mock-api/index.ts b/src/@angor/lib/mock-api/index.ts new file mode 100644 index 0000000..f4e1b61 --- /dev/null +++ b/src/@angor/lib/mock-api/index.ts @@ -0,0 +1 @@ +export * from '@angor/lib/mock-api/public-api'; diff --git a/src/@angor/lib/mock-api/mock-api.constants.ts b/src/@angor/lib/mock-api/mock-api.constants.ts new file mode 100644 index 0000000..79582a8 --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.constants.ts @@ -0,0 +1,10 @@ +import { InjectionToken } from '@angular/core'; + +/** + * Injection token for the default delay of the mock API. + * This token is used to provide a default delay (in milliseconds) + * for all mock API requests if no specific delay is set. + */ +export const ANGOR_MOCK_API_DEFAULT_DELAY = new InjectionToken( + 'ANGOR_MOCK_API_DEFAULT_DELAY' +); diff --git a/src/@angor/lib/mock-api/mock-api.interceptor.ts b/src/@angor/lib/mock-api/mock-api.interceptor.ts new file mode 100644 index 0000000..ca5a2c0 --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.interceptor.ts @@ -0,0 +1,76 @@ +import { + HttpErrorResponse, + HttpEvent, + HttpHandlerFn, + HttpRequest, + HttpResponse, +} from '@angular/common/http'; +import { inject } from '@angular/core'; +import { ANGOR_MOCK_API_DEFAULT_DELAY } from '@angor/lib/mock-api/mock-api.constants'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; +import { Observable, delay, of, switchMap, throwError } from 'rxjs'; +import { AngorMockApiMethods } from './mock-api.types'; + +/** + * Mock API interceptor to intercept HTTP requests and return + * mocked responses if a handler exists for the request. + */ +export const mockApiInterceptor = ( + request: HttpRequest, + next: HttpHandlerFn +): Observable> => { + const defaultDelay = inject(ANGOR_MOCK_API_DEFAULT_DELAY); + const angorMockApiService = inject(AngorMockApiService); + + // Try to find a handler for the current request + const { handler, urlParams } = angorMockApiService.findHandler( + request.method.toUpperCase() as AngorMockApiMethods, + request.url + ); + + // If no handler exists, pass the request to the next handler + if (!handler) { + return next(request); + } + + // Set the intercepted request and URL parameters on the handler + handler.request = request; + handler.urlParams = urlParams; + + // Subscribe to the response function observable + return handler.response.pipe( + delay(handler.delay ?? defaultDelay ?? 0), // Apply handler or default delay + switchMap((response) => { + // If no response is returned, generate a 404 error + if (!response) { + return throwError(() => new HttpErrorResponse({ + error: 'NOT FOUND', + status: 404, + statusText: 'NOT FOUND', + })); + } + + // Parse the response data (status and body) + const data = { + status: response[0], + body: response[1], + }; + + // If status code is between 200 and 300, return a successful response + if (data.status >= 200 && data.status < 300) { + return of(new HttpResponse({ + body: data.body, + status: data.status, + statusText: 'OK', + })); + } + + // For other status codes, throw an error response + return throwError(() => new HttpErrorResponse({ + error: data.body?.error, + status: data.status, + statusText: 'ERROR', + })); + }) + ); +}; diff --git a/src/@angor/lib/mock-api/mock-api.request-handler.ts b/src/@angor/lib/mock-api/mock-api.request-handler.ts new file mode 100644 index 0000000..fed2e4c --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.request-handler.ts @@ -0,0 +1,77 @@ +import { HttpRequest } from '@angular/common/http'; +import { AngorMockApiReplyCallback } from '@angor/lib/mock-api/mock-api.types'; +import { Observable, of, take, throwError } from 'rxjs'; + +export class AngorMockApiHandler { + request!: HttpRequest; + urlParams!: { [key: string]: string }; + + // Private + private _reply: AngorMockApiReplyCallback | undefined; + private _replyCount = 0; + private _replied = 0; + + /** + * Constructor to initialize the handler with a URL and optional delay + * + * @param url - The URL for the mock API handler + * @param delay - Optional delay for the response + */ + constructor(public url: string, public delay?: number) {} + + /** + * Getter for the response observable. + * Executes the reply callback and returns the result as an observable. + * If the callback hasn't been set, or request and execution limits are reached, throws an error. + */ + get response(): Observable { + // Check if the execution limit has been reached + if (this._replyCount > 0 && this._replyCount <= this._replied) { + return throwError(() => new Error('Execution limit has been reached!')); + } + + // Ensure the response callback exists + if (!this._reply) { + return throwError(() => new Error('Response callback function does not exist!')); + } + + // Ensure the request exists + if (!this.request) { + return throwError(() => new Error('Request does not exist!')); + } + + // Increment the replied count + this._replied++; + + // Execute the reply callback + const replyResult = this._reply({ + request: this.request, + urlParams: this.urlParams, + }); + + // Return the result as an observable + if (replyResult instanceof Observable) { + return replyResult.pipe(take(1)); + } + + return of(replyResult).pipe(take(1)); + } + + /** + * Set the reply callback function + * + * @param callback - The callback function to generate mock responses + */ + reply(callback: AngorMockApiReplyCallback): void { + this._reply = callback; + } + + /** + * Set the maximum number of times the mock handler can reply. + * + * @param count - The number of allowed replies + */ + replyCount(count: number): void { + this._replyCount = count; + } +} diff --git a/src/@angor/lib/mock-api/mock-api.service.ts b/src/@angor/lib/mock-api/mock-api.service.ts new file mode 100644 index 0000000..a74c081 --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.service.ts @@ -0,0 +1,158 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiHandler } from '@angor/lib/mock-api/mock-api.request-handler'; +import { AngorMockApiMethods } from '@angor/lib/mock-api/mock-api.types'; +import { compact, fromPairs } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class AngorMockApiService { + private readonly _handlers: Record> = { + get: new Map(), + post: new Map(), + patch: new Map(), + delete: new Map(), + put: new Map(), + head: new Map(), + jsonp: new Map(), + options: new Map(), + }; + + /** + * Find the appropriate handler for a given HTTP method and URL. + * + * @param method - The HTTP method (GET, POST, etc.) + * @param url - The requested URL + * @returns An object containing the handler and the parsed URL parameters + */ + findHandler( + method: AngorMockApiMethods, + url: string + ): { handler: AngorMockApiHandler | undefined; urlParams: Record } { + const matchingHandler = { handler: undefined, urlParams: {} as Record }; + const urlParts = url.split('/'); + const handlers = this._handlers[method.toLowerCase() as AngorMockApiMethods]; + + for (const [handlerUrl, handler] of handlers) { + const handlerUrlParts = handlerUrl.split('/'); + + if (urlParts.length === handlerUrlParts.length) { + const matches = handlerUrlParts.every((part, index) => + part.startsWith(':') || part === urlParts[index] + ); + + if (matches) { + matchingHandler.handler = handler; + matchingHandler.urlParams = fromPairs( + handlerUrlParts + .map((part, index) => (part.startsWith(':') ? [part.substring(1), urlParts[index]] : undefined)) + .filter(Boolean) + ); + break; + } + } + } + + return matchingHandler; + } + + /** + * Register a GET request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onGet(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('get', url, delay); + } + + /** + * Register a POST request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onPost(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('post', url, delay); + } + + /** + * Register a PATCH request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onPatch(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('patch', url, delay); + } + + /** + * Register a DELETE request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onDelete(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('delete', url, delay); + } + + /** + * Register a PUT request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onPut(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('put', url, delay); + } + + /** + * Register a HEAD request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onHead(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('head', url, delay); + } + + /** + * Register a JSONP request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onJsonp(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('jsonp', url, delay); + } + + /** + * Register an OPTIONS request handler. + * + * @param url - The URL for the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + onOptions(url: string, delay?: number): AngorMockApiHandler { + return this._registerHandler('options', url, delay); + } + + /** + * Registers a handler and returns an instance of the handler. + * + * @param method - The HTTP method + * @param url - The URL of the handler + * @param delay - (Optional) Delay for the response in milliseconds + * @returns An instance of AngorMockApiHandler + */ + private _registerHandler(method: AngorMockApiMethods, url: string, delay?: number): AngorMockApiHandler { + const handler = new AngorMockApiHandler(url, delay); + this._handlers[method].set(url, handler); + return handler; + } +} diff --git a/src/@angor/lib/mock-api/mock-api.types.ts b/src/@angor/lib/mock-api/mock-api.types.ts new file mode 100644 index 0000000..d3b7fa8 --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.types.ts @@ -0,0 +1,19 @@ +import { HttpRequest } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +export type AngorMockApiReplyCallback = + | ((data: { + request: HttpRequest; + urlParams: { [key: string]: string }; + }) => [number, string | any] | Observable) + | undefined; + +export type AngorMockApiMethods = + | 'get' + | 'post' + | 'patch' + | 'delete' + | 'put' + | 'head' + | 'jsonp' + | 'options'; diff --git a/src/@angor/lib/mock-api/mock-api.utils.ts b/src/@angor/lib/mock-api/mock-api.utils.ts new file mode 100644 index 0000000..3671ba8 --- /dev/null +++ b/src/@angor/lib/mock-api/mock-api.utils.ts @@ -0,0 +1,26 @@ +export class AngorMockApiUtils { + /** + * Generate a globally unique id + */ + static guid(): string { + /* eslint-disable */ + + let d = new Date().getTime(); + + // Use high-precision timer if available + if ( + typeof performance !== 'undefined' && + typeof performance.now === 'function' + ) { + d += performance.now(); + } + + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + const r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16); + }); + + /* eslint-enable */ + } +} diff --git a/src/@angor/lib/mock-api/public-api.ts b/src/@angor/lib/mock-api/public-api.ts new file mode 100644 index 0000000..58e3bb7 --- /dev/null +++ b/src/@angor/lib/mock-api/public-api.ts @@ -0,0 +1,5 @@ +export * from '@angor/lib/mock-api/mock-api.constants'; +export * from '@angor/lib/mock-api/mock-api.interceptor'; +export * from '@angor/lib/mock-api/mock-api.service'; +export * from '@angor/lib/mock-api/mock-api.types'; +export * from '@angor/lib/mock-api/mock-api.utils'; diff --git a/src/@angor/pipes/find-by-key/find-by-key.pipe.ts b/src/@angor/pipes/find-by-key/find-by-key.pipe.ts new file mode 100644 index 0000000..44ba391 --- /dev/null +++ b/src/@angor/pipes/find-by-key/find-by-key.pipe.ts @@ -0,0 +1,32 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +/** + * A pipe that finds an object or objects from a given source array using key-value pairs. + */ +@Pipe({ + name: 'angorFindByKey', + pure: false, + standalone: true, +}) +export class AngorFindByKeyPipe implements PipeTransform { + /** + * Transforms the input value by finding corresponding objects from the source array + * based on the specified key-value pair. + * + * @param value A string or an array of strings to match in the source array. + * @param key The key of the object property to search by. + * @param source The array of objects to search within. + * @returns A single object if `value` is a string, or an array of objects if `value` is an array. + */ + transform(value: string | string[], key: string, source: any[]): any | any[] { + // If value is an array of strings, map each to its corresponding object in the source. + if (Array.isArray(value)) { + return value.map((item) => + source.find((sourceItem) => sourceItem[key] === item) + ); + } + + // Otherwise, treat value as a single string and find the corresponding object. + return source.find((sourceItem) => sourceItem[key] === value); + } +} diff --git a/src/@angor/pipes/find-by-key/index.ts b/src/@angor/pipes/find-by-key/index.ts new file mode 100644 index 0000000..7092cdb --- /dev/null +++ b/src/@angor/pipes/find-by-key/index.ts @@ -0,0 +1 @@ +export * from '@angor/pipes/find-by-key/public-api'; diff --git a/src/@angor/pipes/find-by-key/public-api.ts b/src/@angor/pipes/find-by-key/public-api.ts new file mode 100644 index 0000000..39bd448 --- /dev/null +++ b/src/@angor/pipes/find-by-key/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/pipes/find-by-key/find-by-key.pipe'; diff --git a/src/@angor/services/config/config.constants.ts b/src/@angor/services/config/config.constants.ts new file mode 100644 index 0000000..5b33a76 --- /dev/null +++ b/src/@angor/services/config/config.constants.ts @@ -0,0 +1,3 @@ +import { InjectionToken } from '@angular/core'; + +export const ANGOR_CONFIG = new InjectionToken('ANGOR_APP_CONFIG'); diff --git a/src/@angor/services/config/config.service.ts b/src/@angor/services/config/config.service.ts new file mode 100644 index 0000000..4199db8 --- /dev/null +++ b/src/@angor/services/config/config.service.ts @@ -0,0 +1,35 @@ +import { inject, Injectable } from '@angular/core'; +import { ANGOR_CONFIG } from '@angor/services/config/config.constants'; +import { merge } from 'lodash-es'; +import { BehaviorSubject, Observable } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AngorConfigService { + private readonly _defaultConfig = inject(ANGOR_CONFIG); + private readonly _configSubject = new BehaviorSubject(this._defaultConfig); + + /** + * Getter for config as an Observable. + */ + get config$(): Observable { + return this._configSubject.asObservable(); + } + + /** + * Setter for config. + * Merges the provided value with the current config and emits the new value. + * + * @param value - Partial configuration object to merge with the existing config. + */ + set config(value: any) { + const updatedConfig = merge({}, this._configSubject.getValue(), value); + this._configSubject.next(updatedConfig); + } + + /** + * Resets the configuration to the default value. + */ + reset(): void { + this._configSubject.next(this._defaultConfig); + } +} diff --git a/src/@angor/services/config/config.types.ts b/src/@angor/services/config/config.types.ts new file mode 100644 index 0000000..5eda7b0 --- /dev/null +++ b/src/@angor/services/config/config.types.ts @@ -0,0 +1,17 @@ +// Types +export type Scheme = 'auto' | 'dark' | 'light'; +export type Screens = Record; +export type Theme = 'theme-default' | string; +export type Themes = Array<{ id: string; name: string }>; + +/** + * AngorConfig interface to strictly type the app's configuration object. + * This ensures consistency when defining or updating app settings. + */ +export interface AngorConfig { + layout: string; // Layout type (e.g., 'vertical', 'horizontal') + scheme: Scheme; // Color scheme: 'auto', 'dark', or 'light' + screens: Screens; // Screen breakpoints, e.g., { 'xs': '600px', ... } + theme: Theme; // Active theme identifier, e.g., 'theme-default' + themes: Themes; // List of available themes, each with an id and name +} diff --git a/src/@angor/services/config/index.ts b/src/@angor/services/config/index.ts new file mode 100644 index 0000000..b5adda9 --- /dev/null +++ b/src/@angor/services/config/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/config/public-api'; diff --git a/src/@angor/services/config/public-api.ts b/src/@angor/services/config/public-api.ts new file mode 100644 index 0000000..6616a7d --- /dev/null +++ b/src/@angor/services/config/public-api.ts @@ -0,0 +1,2 @@ +export * from '@angor/services/config/config.service'; +export * from '@angor/services/config/config.types'; diff --git a/src/@angor/services/confirmation/confirmation.service.ts b/src/@angor/services/confirmation/confirmation.service.ts new file mode 100644 index 0000000..c48c2c2 --- /dev/null +++ b/src/@angor/services/confirmation/confirmation.service.ts @@ -0,0 +1,54 @@ +import { inject, Injectable } from '@angular/core'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { AngorConfirmationConfig } from '@angor/services/confirmation/confirmation.types'; +import { AngorConfirmationDialogComponent } from '@angor/services/confirmation/dialog/dialog.component'; +import { merge } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class AngorConfirmationService { + private readonly _matDialog = inject(MatDialog); + + // Default configuration for the confirmation dialog + private readonly _defaultConfig: AngorConfirmationConfig = { + title: 'Confirm action', + message: 'Are you sure you want to confirm this action?', + icon: { + show: true, + name: 'heroicons_outline:exclamation-triangle', + color: 'warn', + }, + actions: { + confirm: { + show: true, + label: 'Confirm', + color: 'warn', + }, + cancel: { + show: true, + label: 'Cancel', + }, + }, + dismissible: false, + }; + + /** + * Opens a confirmation dialog with the provided or default configuration. + * + * @param config - User-defined configuration that overrides the default. + * @returns A reference to the opened confirmation dialog. + */ + open( + config: AngorConfirmationConfig = {} + ): MatDialogRef { + // Merge the user configuration with the default configuration + const mergedConfig = merge({}, this._defaultConfig, config); + + // Open and return the MatDialog reference + return this._matDialog.open(AngorConfirmationDialogComponent, { + autoFocus: false, + disableClose: !mergedConfig.dismissible, + data: mergedConfig, + panelClass: 'angor-confirmation-dialog-panel', + }); + } +} diff --git a/src/@angor/services/confirmation/confirmation.types.ts b/src/@angor/services/confirmation/confirmation.types.ts new file mode 100644 index 0000000..b01cb70 --- /dev/null +++ b/src/@angor/services/confirmation/confirmation.types.ts @@ -0,0 +1,36 @@ +export interface AngorConfirmationConfig { + title?: string; + message?: string; + icon?: { + show?: boolean; + name?: string; + color?: IconColor; + }; + actions?: { + confirm?: ActionConfig; + cancel?: ActionConfig; + }; + dismissible?: boolean; +} + +/** + * A type for icon color options used in confirmation dialogs. + */ +export type IconColor = + | 'primary' + | 'accent' + | 'warn' + | 'basic' + | 'info' + | 'success' + | 'warning' + | 'error'; + +/** + * A common structure for confirm and cancel action configurations. + */ +export interface ActionConfig { + show?: boolean; + label?: string; + color?: 'primary' | 'accent' | 'warn'; +} diff --git a/src/@angor/services/confirmation/dialog/dialog.component.html b/src/@angor/services/confirmation/dialog/dialog.component.html new file mode 100644 index 0000000..36e0d1c --- /dev/null +++ b/src/@angor/services/confirmation/dialog/dialog.component.html @@ -0,0 +1,95 @@ +
+ + @if (data.dismissible) { +
+ +
+ } + + +
+ + @if (data.icon.show) { +
+ +
+ } + + @if (data.title || data.message) { +
+ + @if (data.title) { +
+ } + + + @if (data.message) { +
+ } +
+ } +
+ + + @if (data.actions.confirm.show || data.actions.cancel.show) { +
+ + @if (data.actions.cancel.show) { + + } + + + @if (data.actions.confirm.show) { + + } +
+ } +
diff --git a/src/@angor/services/confirmation/dialog/dialog.component.ts b/src/@angor/services/confirmation/dialog/dialog.component.ts new file mode 100644 index 0000000..455a491 --- /dev/null +++ b/src/@angor/services/confirmation/dialog/dialog.component.ts @@ -0,0 +1,32 @@ +import { NgClass } from '@angular/common'; +import { Component, ViewEncapsulation, inject } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { AngorConfirmationConfig } from '@angor/services/confirmation/confirmation.types'; + +@Component({ + selector: 'angor-confirmation-dialog', + templateUrl: './dialog.component.html', + styles: [ + ` + .angor-confirmation-dialog-panel { + @screen md { + @apply w-128; + } + + .mat-mdc-dialog-container { + .mat-mdc-dialog-surface { + padding: 0 !important; + } + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [MatButtonModule, MatDialogModule, MatIconModule, NgClass], +}) +export class AngorConfirmationDialogComponent { + data: AngorConfirmationConfig = inject(MAT_DIALOG_DATA); +} diff --git a/src/@angor/services/confirmation/index.ts b/src/@angor/services/confirmation/index.ts new file mode 100644 index 0000000..f4ab1f0 --- /dev/null +++ b/src/@angor/services/confirmation/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/confirmation/public-api'; diff --git a/src/@angor/services/confirmation/public-api.ts b/src/@angor/services/confirmation/public-api.ts new file mode 100644 index 0000000..640834a --- /dev/null +++ b/src/@angor/services/confirmation/public-api.ts @@ -0,0 +1,2 @@ +export * from '@angor/services/confirmation/confirmation.service'; +export * from '@angor/services/confirmation/confirmation.types'; diff --git a/src/@angor/services/loading/index.ts b/src/@angor/services/loading/index.ts new file mode 100644 index 0000000..b20b10c --- /dev/null +++ b/src/@angor/services/loading/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/loading/public-api'; diff --git a/src/@angor/services/loading/loading.interceptor.ts b/src/@angor/services/loading/loading.interceptor.ts new file mode 100644 index 0000000..c715411 --- /dev/null +++ b/src/@angor/services/loading/loading.interceptor.ts @@ -0,0 +1,31 @@ +import { HttpEvent, HttpHandlerFn, HttpRequest } from '@angular/common/http'; +import { inject } from '@angular/core'; +import { AngorLoadingService } from '@angor/services/loading/loading.service'; +import { Observable, finalize, take } from 'rxjs'; + +export const angorLoadingInterceptor = ( + req: HttpRequest, + next: HttpHandlerFn +): Observable> => { + const angorLoadingService = inject(AngorLoadingService); + let handleRequestsAutomatically = false; + + angorLoadingService.auto$.pipe(take(1)).subscribe((value) => { + handleRequestsAutomatically = value; + }); + + // If the Auto mode is turned off, do nothing + if (!handleRequestsAutomatically) { + return next(req); + } + + // Set the loading status to true + angorLoadingService._setLoadingStatus(true, req.url); + + return next(req).pipe( + finalize(() => { + // Set the status to false if there are any errors or the request is completed + angorLoadingService._setLoadingStatus(false, req.url); + }) + ); +}; diff --git a/src/@angor/services/loading/loading.service.ts b/src/@angor/services/loading/loading.service.ts new file mode 100644 index 0000000..612df61 --- /dev/null +++ b/src/@angor/services/loading/loading.service.ts @@ -0,0 +1,126 @@ +import { Injectable } from '@angular/core'; +import { BehaviorSubject, Observable } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AngorLoadingService { + private _auto$: BehaviorSubject = new BehaviorSubject( + true + ); + private _mode$: BehaviorSubject<'determinate' | 'indeterminate'> = + new BehaviorSubject<'determinate' | 'indeterminate'>('indeterminate'); + private _progress$: BehaviorSubject = new BehaviorSubject< + number | null + >(0); + private _show$: BehaviorSubject = new BehaviorSubject( + false + ); + private _urlMap: Map = new Map(); + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for auto mode + */ + get auto$(): Observable { + return this._auto$.asObservable(); + } + + /** + * Getter for mode + */ + get mode$(): Observable<'determinate' | 'indeterminate'> { + return this._mode$.asObservable(); + } + + /** + * Getter for progress + */ + get progress$(): Observable { + return this._progress$.asObservable(); + } + + /** + * Getter for show + */ + get show$(): Observable { + return this._show$.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Show the loading bar + */ + show(): void { + this._show$.next(true); + } + + /** + * Hide the loading bar + */ + hide(): void { + this._show$.next(false); + } + + /** + * Set the auto mode + * + * @param value + */ + setAutoMode(value: boolean): void { + this._auto$.next(value); + } + + /** + * Set the mode + * + * @param value + */ + setMode(value: 'determinate' | 'indeterminate'): void { + this._mode$.next(value); + } + + /** + * Set the progress of the bar manually + * + * @param value + */ + setProgress(value: number): void { + if (value < 0 || value > 100) { + console.error('Progress value must be between 0 and 100!'); + return; + } + + this._progress$.next(value); + } + + /** + * Sets the loading status on the given url + * + * @param status + * @param url + */ + _setLoadingStatus(status: boolean, url: string): void { + // Return if the url was not provided + if (!url) { + console.error('The request URL must be provided!'); + return; + } + + if (status === true) { + this._urlMap.set(url, status); + this._show$.next(true); + } else if (status === false && this._urlMap.has(url)) { + this._urlMap.delete(url); + } + + // Only set the status to 'false' if all outgoing requests are completed + if (this._urlMap.size === 0) { + this._show$.next(false); + } + } +} diff --git a/src/@angor/services/loading/public-api.ts b/src/@angor/services/loading/public-api.ts new file mode 100644 index 0000000..d99a7ab --- /dev/null +++ b/src/@angor/services/loading/public-api.ts @@ -0,0 +1,2 @@ +export * from '@angor/services/loading/loading.interceptor'; +export * from '@angor/services/loading/loading.service'; diff --git a/src/@angor/services/media-watcher/index.ts b/src/@angor/services/media-watcher/index.ts new file mode 100644 index 0000000..e81d26d --- /dev/null +++ b/src/@angor/services/media-watcher/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/media-watcher/public-api'; diff --git a/src/@angor/services/media-watcher/media-watcher.service.ts b/src/@angor/services/media-watcher/media-watcher.service.ts new file mode 100644 index 0000000..8e14c95 --- /dev/null +++ b/src/@angor/services/media-watcher/media-watcher.service.ts @@ -0,0 +1,100 @@ +import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; +import { Injectable, inject } from '@angular/core'; +import { AngorConfigService } from '@angor/services/config'; +import { fromPairs } from 'lodash-es'; +import { Observable, ReplaySubject, map, switchMap } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AngorMediaWatcherService { + private _breakpointObserver = inject(BreakpointObserver); + private _angorConfigService = inject(AngorConfigService); + + private _onMediaChange: ReplaySubject<{ + matchingAliases: string[]; + matchingQueries: any; + }> = new ReplaySubject<{ matchingAliases: string[]; matchingQueries: any }>( + 1 + ); + + /** + * Constructor + */ + constructor() { + this._angorConfigService.config$ + .pipe( + map((config) => + fromPairs( + Object.entries(config.screens).map( + ([alias, screen]) => [ + alias, + `(min-width: ${screen})`, + ] + ) + ) + ), + switchMap((screens) => + this._breakpointObserver + .observe(Object.values(screens)) + .pipe( + map((state) => { + // Prepare the observable values and set their defaults + const matchingAliases: string[] = []; + const matchingQueries: any = {}; + + // Get the matching breakpoints and use them to fill the subject + const matchingBreakpoints = + Object.entries(state.breakpoints).filter( + ([query, matches]) => matches + ) ?? []; + for (const [query] of matchingBreakpoints) { + // Find the alias of the matching query + const matchingAlias = Object.entries( + screens + ).find(([alias, q]) => q === query)[0]; + + // Add the matching query to the observable values + if (matchingAlias) { + matchingAliases.push(matchingAlias); + matchingQueries[matchingAlias] = query; + } + } + + // Execute the observable + this._onMediaChange.next({ + matchingAliases, + matchingQueries, + }); + }) + ) + ) + ) + .subscribe(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for _onMediaChange + */ + get onMediaChange$(): Observable<{ + matchingAliases: string[]; + matchingQueries: any; + }> { + return this._onMediaChange.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * On media query change + * + * @param query + */ + onMediaQueryChange$(query: string | string[]): Observable { + return this._breakpointObserver.observe(query); + } +} diff --git a/src/@angor/services/media-watcher/public-api.ts b/src/@angor/services/media-watcher/public-api.ts new file mode 100644 index 0000000..0d35919 --- /dev/null +++ b/src/@angor/services/media-watcher/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/services/media-watcher/media-watcher.service'; diff --git a/src/@angor/services/platform/index.ts b/src/@angor/services/platform/index.ts new file mode 100644 index 0000000..ab2159b --- /dev/null +++ b/src/@angor/services/platform/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/platform/public-api'; diff --git a/src/@angor/services/platform/platform.service.ts b/src/@angor/services/platform/platform.service.ts new file mode 100644 index 0000000..12370a9 --- /dev/null +++ b/src/@angor/services/platform/platform.service.ts @@ -0,0 +1,49 @@ +import { Platform } from '@angular/cdk/platform'; +import { inject, Injectable } from '@angular/core'; + +@Injectable({ providedIn: 'root' }) +export class AngorPlatformService { + private _platform = inject(Platform); + + osName = 'os-unknown'; + + /** + * Constructor + */ + constructor() { + // If the platform is not a browser, return immediately + if (!this._platform.isBrowser) { + return; + } + + // Windows + if (navigator.userAgent.includes('Win')) { + this.osName = 'os-windows'; + } + + // Mac OS + if (navigator.userAgent.includes('Mac')) { + this.osName = 'os-mac'; + } + + // Unix + if (navigator.userAgent.includes('X11')) { + this.osName = 'os-unix'; + } + + // Linux + if (navigator.userAgent.includes('Linux')) { + this.osName = 'os-linux'; + } + + // iOS + if (this._platform.IOS) { + this.osName = 'os-ios'; + } + + // Android + if (this._platform.ANDROID) { + this.osName = 'os-android'; + } + } +} diff --git a/src/@angor/services/platform/public-api.ts b/src/@angor/services/platform/public-api.ts new file mode 100644 index 0000000..2d45b5e --- /dev/null +++ b/src/@angor/services/platform/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/services/platform/platform.service'; diff --git a/src/@angor/services/splash-screen/index.ts b/src/@angor/services/splash-screen/index.ts new file mode 100644 index 0000000..14bcd4a --- /dev/null +++ b/src/@angor/services/splash-screen/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/splash-screen/public-api'; diff --git a/src/@angor/services/splash-screen/public-api.ts b/src/@angor/services/splash-screen/public-api.ts new file mode 100644 index 0000000..748aed7 --- /dev/null +++ b/src/@angor/services/splash-screen/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/services/splash-screen/splash-screen.service'; diff --git a/src/@angor/services/splash-screen/splash-screen.service.ts b/src/@angor/services/splash-screen/splash-screen.service.ts new file mode 100644 index 0000000..cae0474 --- /dev/null +++ b/src/@angor/services/splash-screen/splash-screen.service.ts @@ -0,0 +1,43 @@ +import { DOCUMENT } from '@angular/common'; +import { inject, Injectable } from '@angular/core'; +import { NavigationEnd, Router } from '@angular/router'; +import { filter, take } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AngorSplashScreenService { + private _document = inject(DOCUMENT); + private _router = inject(Router); + + /** + * Constructor + */ + constructor() { + // Hide it on the first NavigationEnd event + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + take(1) + ) + .subscribe(() => { + this.hide(); + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Show the splash screen + */ + show(): void { + this._document.body.classList.remove('angor-splash-screen-hidden'); + } + + /** + * Hide the splash screen + */ + hide(): void { + this._document.body.classList.add('angor-splash-screen-hidden'); + } +} diff --git a/src/@angor/services/utils/index.ts b/src/@angor/services/utils/index.ts new file mode 100644 index 0000000..4180a13 --- /dev/null +++ b/src/@angor/services/utils/index.ts @@ -0,0 +1 @@ +export * from '@angor/services/utils/public-api'; diff --git a/src/@angor/services/utils/public-api.ts b/src/@angor/services/utils/public-api.ts new file mode 100644 index 0000000..172ce0d --- /dev/null +++ b/src/@angor/services/utils/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/services/utils/utils.service'; diff --git a/src/@angor/services/utils/utils.service.ts b/src/@angor/services/utils/utils.service.ts new file mode 100644 index 0000000..4a55166 --- /dev/null +++ b/src/@angor/services/utils/utils.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@angular/core'; +import { IsActiveMatchOptions } from '@angular/router'; + +@Injectable({ providedIn: 'root' }) +export class AngorUtilsService { + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Get the equivalent "IsActiveMatchOptions" options for "exact = true". + */ + get exactMatchOptions(): IsActiveMatchOptions { + return { + paths: 'exact', + fragment: 'ignored', + matrixParams: 'ignored', + queryParams: 'exact', + }; + } + + /** + * Get the equivalent "IsActiveMatchOptions" options for "exact = false". + */ + get subsetMatchOptions(): IsActiveMatchOptions { + return { + paths: 'subset', + fragment: 'ignored', + matrixParams: 'ignored', + queryParams: 'subset', + }; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Generates a random id + * + * @param length + */ + randomId(length: number = 10): string { + const chars = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + let name = ''; + + for (let i = 0; i < 10; i++) { + name += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + return name; + } +} diff --git a/src/@angor/styles/components/example-viewer.scss b/src/@angor/styles/components/example-viewer.scss new file mode 100644 index 0000000..157feb3 --- /dev/null +++ b/src/@angor/styles/components/example-viewer.scss @@ -0,0 +1,44 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Example viewer +/* ----------------------------------------------------------------------------------------------------- */ +.example-viewer { + display: flex; + flex-direction: column; + margin: 32px 0; + overflow: hidden; + @apply bg-card rounded-2xl shadow; + + .title { + display: flex; + align-items: center; + justify-content: space-between; + height: 88px; + min-height: 88px; + max-height: 88px; + padding: 0 40px; + + h6 { + font-weight: 700; + } + + .controls { + display: flex; + align-items: center; + + > * + * { + margin-left: 8px; + } + } + } + + mat-tab-group { + .mat-tab-body-content { + .angor-highlight { + pre { + margin: 0; + border-radius: 0; + } + } + } + } +} diff --git a/src/@angor/styles/components/input.scss b/src/@angor/styles/components/input.scss new file mode 100644 index 0000000..391b400 --- /dev/null +++ b/src/@angor/styles/components/input.scss @@ -0,0 +1,43 @@ +input, +textarea { + background: transparent; + + /* Placeholder color */ + &::placeholder { + @apply text-hint; + } + + &::-moz-placeholder { + @apply text-hint; + } + + &::-webkit-input-placeholder { + @apply text-hint; + } + + &:-ms-input-placeholder { + @apply text-hint; + } + + /* Autofill color fix */ + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-background-clip: text; + transition: background-color 5000s !important; + } + + .dark & { + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-text-fill-color: rgba(255, 255, 255, 0.87); + } + } + + [data-autocompleted] { + background-color: transparent !important; + } +} diff --git a/src/@angor/styles/main.scss b/src/@angor/styles/main.scss new file mode 100644 index 0000000..41f69c2 --- /dev/null +++ b/src/@angor/styles/main.scss @@ -0,0 +1,9 @@ +/* 1. Components */ +@use 'components/example-viewer'; +@use 'components/input'; + +/* 2. Overrides */ +@use 'overrides/angular-material'; +@use 'overrides/highlightjs'; +@use 'overrides/perfect-scrollbar'; +@use 'overrides/quill'; diff --git a/src/@angor/styles/overrides/angular-material.scss b/src/@angor/styles/overrides/angular-material.scss new file mode 100644 index 0000000..d40b2d5 --- /dev/null +++ b/src/@angor/styles/overrides/angular-material.scss @@ -0,0 +1,1283 @@ +/* -------------------------------------------------------------------------- */ +/* @ Overlay +/* -------------------------------------------------------------------------- */ +.angor-backdrop-on-mobile { + @apply bg-black bg-opacity-60 sm:bg-transparent #{'!important'}; +} + +/* -------------------------------------------------------------------------- */ +/* @ Font smoothing +/* -------------------------------------------------------------------------- */ +*[class*='mat-'], +*[class*='mat-mdc-'] { + -webkit-font-smoothing: auto !important; + -moz-osx-font-smoothing: auto !important; + + * { + -webkit-font-smoothing: auto !important; + -moz-osx-font-smoothing: auto !important; + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Accordion +/* -------------------------------------------------------------------------- */ +.mat-accordion { + .mat-expansion-panel { + margin-bottom: 24px; + border-radius: 8px !important; + transition: box-shadow 225ms cubic-bezier(0.4, 0, 0.2, 1); + @apply shadow #{'!important'}; + + &:last-child { + margin-bottom: 0; + } + + &.mat-expanded, + &:hover { + @apply shadow-lg #{'!important'}; + } + + &:not(.mat-expanded) { + .mat-expansion-panel-header { + &:not([aria-disabled='true']) { + &.cdk-keyboard-focused, + &.cdk-program-focused, + &:hover { + background: transparent !important; + } + } + } + } + + .mat-expansion-panel-header { + font-size: 14px; + + &[aria-disabled='true'] { + .mat-expansion-panel-header-description { + margin-right: 28px; + } + } + + .mat-expansion-indicator { + display: inline-flex; + align-items: center; + justify-content: center; + width: 12px; + height: 12px; + + /* Do not override the border color of the expansion panel indicator */ + &:after { + border-color: currentColor !important; + } + } + } + + .mat-expansion-panel-body { + @apply text-secondary #{'!important'}; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Buttons +/* -------------------------------------------------------------------------- */ +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-outlined-button, +.mat-mdc-unelevated-button, +.mat-mdc-icon-button, +.mat-mdc-fab, +.mat-mdc-mini-fab { + height: 40px; + min-height: 40px; + max-height: 40px; + line-height: 1 !important; + + /* Large button */ + &.angor-mat-button-large { + height: 48px; + min-height: 48px; + max-height: 48px; + } + + /* Lower the icon opacity on disabled buttons */ + &[disabled='true'] { + .mat-icon { + opacity: 0.38 !important; + } + } +} + +/* Icon buttons */ +.mat-mdc-icon-button { + display: inline-flex !important; + align-items: center; + justify-content: center; + width: 40px !important; + padding: 0 !important; + + svg, + img { + height: auto !important; + } +} + +/* FAB buttons */ +.mat-mdc-fab { + max-height: 56px; + border-radius: 16px !important; + + &:not(.mdc-fab--extended) .mdc-fab__ripple { + border-radius: 16px !important; + } +} + +/* Mini FAB buttons */ +.mat-mdc-mini-fab { + border-radius: 12px !important; + + &:not(.mdc-fab--extended) .mdc-fab__ripple { + border-radius: 12px !important; + } +} + +/* Rounded design */ +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-outlined-button, +.mat-mdc-unelevated-button { + padding: 0 20px !important; + border-radius: 9999px !important; +} + +/* Fix the alignment of icons when used within buttons */ +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-outlined-button, +.mat-mdc-unelevated-button { + & > .mat-icon { + margin-left: 0 !important; + margin-right: 0 !important; + } +} + +/* Adjust the color of mat-progress-spinner when used within buttons */ +.mat-mdc-button, +.mat-mdc-raised-button, +.mat-mdc-outlined-button, +.mat-mdc-unelevated-button, +.mat-mdc-icon-button, +.mat-mdc-fab, +.mat-mdc-mini-fab { + .mat-mdc-progress-spinner { + .mdc-circular-progress__indeterminate-container { + circle { + stroke: currentColor !important; + animation-duration: 6000ms; + } + } + } +} + +/* Adjust the focus, ripple and icon colors of colored background buttons */ +.mat-mdc-raised-button, +.mat-mdc-unelevated-button, +.mat-mdc-fab, +.mat-mdc-mini-fab { + --mat-mdc-button-persistent-ripple-color: theme( + 'colors.gray[400]' + ) !important; + --mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important; + + .dark & { + --mat-mdc-button-persistent-ripple-color: theme( + 'colors.black' + ) !important; + --mat-mdc-button-ripple-color: rgba(0, 0, 0, 0.1) !important; + } + + .mat-icon { + color: currentColor !important; + } + + .mat-ripple-element { + @apply bg-black/10 #{'!important'}; + } +} + +/* Color the icons of transparent background buttons */ +.mat-mdc-button, +.mat-mdc-icon-button, +.mat-mdc-outlined-button { + &:not([disabled='true']) { + /* Apply primary color */ + &.mat-primary { + .mat-icon { + @apply text-primary #{'!important'}; + } + } + + /* Apply accent color */ + &.mat-accent { + .mat-icon { + @apply text-accent #{'!important'}; + } + } + + /* Apply warn color */ + &.mat-warn { + .mat-icon { + @apply text-warn #{'!important'}; + } + } + } +} + +/* Adjust the border color of outlined buttons */ +.mat-mdc-outlined-button { + /* Not disabled */ + &:not([disabled='true']) { + @apply border-gray-300 dark:border-gray-500 #{'!important'}; + } + + /* Disabled */ + &[disabled='true'] { + @apply border-gray-300/70 dark:border-gray-600 #{'!important'}; + } +} + +/* Don't wrap the button label text */ +.mdc-button { + .mdc-button__label { + white-space: nowrap; + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Button Toggle +/* -------------------------------------------------------------------------- */ +.mat-button-toggle-group { + border: none !important; + @apply space-x-1; + + &.mat-button-toggle-group-appearance-standard { + .mat-button-toggle + .mat-button-toggle { + background-clip: padding-box; + } + } + + .mat-button-toggle { + border-radius: 9999px; + overflow: hidden; + border: none !important; + font-weight: 500; + + &.mat-button-toggle-checked { + .mat-button-toggle-label-content { + @apply text-default #{'!important'}; + } + } + + .mat-button-toggle-label-content { + padding: 0 20px; + line-height: 40px !important; + @apply text-secondary; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Checkbox +/* -------------------------------------------------------------------------- */ +.mat-mdc-checkbox { + display: inline-flex !important; + + .mdc-form-field { + padding-right: 12px; + } +} + +.mdc-checkbox__native-control { + opacity: 0 !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Chip +/* -------------------------------------------------------------------------- */ +.mat-mdc-chip { + font-weight: 500 !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Dialog +/* -------------------------------------------------------------------------- */ +.mat-mdc-dialog-container { + .mdc-dialog__surface { + border-radius: 16px !important; + padding: 24px; + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Drawer +/* -------------------------------------------------------------------------- */ +.mat-drawer-backdrop.mat-drawer-shown { + background-color: rgba(0, 0, 0, 0.6) !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Form fields +/* -------------------------------------------------------------------------- */ + +/* "fill" appearance */ +.mat-mdc-form-field.mat-form-field-appearance-fill { + /* Disabled */ + &.mat-form-field-disabled { + opacity: 0.7 !important; + } + + /* Invalid */ + &.mat-form-field-invalid { + /* Border color */ + .mat-mdc-text-field-wrapper { + @apply border-warn dark:border-warn #{'!important'}; + } + + /* Select */ + .mat-mdc-select { + /* Placeholder color */ + .mat-mdc-select-placeholder { + @apply text-warn #{'!important'}; + } + } + } + + /* Hover */ + &:hover { + .mat-mdc-form-field-focus-overlay { + opacity: 0 !important; + } + } + + /* Focused */ + &.mat-focused { + .mat-mdc-form-field-focus-overlay { + opacity: 0 !important; + } + } + + /* Focused and valid fields */ + &.mat-focused:not(.mat-form-field-invalid) { + /* Border color */ + .mat-mdc-text-field-wrapper { + @apply border-primary dark:border-primary #{'!important'}; + } + } + + /* Remove the default arrow for native select */ + &.mat-mdc-form-field-type-mat-native-select { + .mat-mdc-form-field-infix { + select { + top: auto; + margin-top: 0; + margin-bottom: 0; + padding-top: 0; + padding-right: 18px; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%2364748B' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5H7z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right -7px center; + background-size: 24px; + + .dark & { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%2397a6ba' viewBox='0 0 24 24'%3E%3Cpath d='M7 10l5 5 5-5H7z'/%3E%3C/svg%3E"); + } + } + + &:after { + display: none; + } + } + } + + /* Default style tweaks and enhancements */ + .mat-mdc-text-field-wrapper { + padding: 0; + border-radius: 6px; + border-width: 1px; + border-style: solid; + @apply border-gray-300 bg-white shadow-sm dark:border-gray-500 dark:bg-black dark:bg-opacity-5 #{'!important'}; + + /* Adjust the top spacing and overflow when mat-label present */ + &:not(.mdc-text-field--no-label) { + margin-top: 24px; + overflow: visible; + } + + .mat-mdc-form-field-focus-overlay { + border-radius: 6px; + } + + /* Form field */ + .mat-mdc-form-field-flex { + position: relative; + display: flex; + align-items: stretch; + border-radius: 6px; + padding: 0 16px; + + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix { + padding: 0 !important; + + > .mat-icon { + margin-right: 12px; + padding: 0 !important; + } + + > .mat-mdc-icon-button { + margin: 0 4px 0 -10px; + } + + > .mat-mdc-select { + margin-right: 10px; + } + + > .mat-datepicker-toggle { + margin-left: -8px; + } + } + + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + padding: 0 !important; + + > .mat-icon { + margin-left: 12px; + padding: 0 !important; + } + + > .mat-mdc-icon-button { + margin: 0 -10px 0 4px; + } + + > .mat-mdc-select { + margin-left: 10px; + } + + > .mat-datepicker-toggle { + margin-right: -8px; + } + } + + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + display: inline-flex; + align-items: center; + justify-content: center; + @apply text-hint #{'!important'}; + + .mat-mdc-icon-button { + width: 40px; + min-width: 40px; + height: 40px; + min-height: 40px; + } + + .mat-icon, + .mat-mdc-icon-button:not([disabled]), + .mat-mdc-select-value { + @apply text-hint; + } + + /* Datepicker default icon size */ + .mat-datepicker-toggle-default-icon { + @apply icon-size-6; + } + + /* Make mat-select usable as prefix and suffix */ + .mat-mdc-select { + display: flex; + align-items: center; + + &:focus { + .mat-mdc-select-trigger { + .mat-mdc-select-value { + @apply text-primary #{'!important'}; + } + + .mat-mdc-select-arrow-wrapper { + .mat-mdc-select-arrow { + border-top-color: var( + --angor-primary + ) !important; + } + } + } + } + + .mat-mdc-select-trigger { + display: flex; + align-items: center; + + .mat-mdc-select-value { + display: flex; + max-width: none; + + mat-mdc-select-trigger { + .mat-icon { + margin: 0 !important; + } + } + } + + .mat-mdc-select-arrow-wrapper { + display: flex; + align-items: center; + transform: none; + margin-left: 4px; + + .mat-mdc-select-arrow { + min-height: 0; + @apply text-gray-500 dark:text-gray-400 #{'!important'}; + } + } + } + } + } + + /* Infix */ + .mat-mdc-form-field-infix { + position: static; + display: flex; + align-items: center; + width: 88px; + min-height: 48px; + padding: 0; + border: 0; + + /* Floating label - disable floating action */ + .mat-mdc-floating-label { + top: -25px !important; + left: 0 !important; + width: 100% !important; + transform: none !important; + pointer-events: auto; + font-weight: 500; + @apply text-default #{'!important'}; + } + + /* Textarea */ + textarea.mat-mdc-input-element { + margin: 12px 0; + padding: 0 6px 0 0; + } + + /* Chips */ + .mat-mdc-chip-set { + width: 100%; + margin: 0 -8px; + } + } + } + + /* Remove the underline */ + .mdc-line-ripple { + display: none; + } + } + + /* Subscript tweaks */ + .mat-mdc-form-field-subscript-wrapper { + font-size: 12px; + font-weight: 500; + + .mat-mdc-form-field-hint-wrapper, + .mat-mdc-form-field-error-wrapper { + padding: 0; + } + + .mat-mdc-form-field-hint { + @apply text-hint #{'!important'}; + } + } + + /* Adds better alignment for textarea inputs */ + &:has(textarea.mat-mdc-input-element) { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + align-self: flex-start; + padding-top: 14px !important; + } + } + } + } + + /* Rounded */ + &.angor-mat-rounded { + .mat-mdc-text-field-wrapper { + border-radius: 24px; + } + + /* Emphasized affix */ + &.angor-mat-emphasized-affix { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix { + border-radius: 24px 0 0 24px; + + > .mat-icon { + margin-right: 12px; + } + + > .mat-mdc-icon-button { + margin: 0 2px 0 -10px !important; + } + + > .mat-mdc-select { + margin-right: 8px; + } + + > .mat-datepicker-toggle { + margin-right: 4px; + } + + > *:not(.mat-icon):not(.mat-mdc-icon-button):not( + .mat-mdc-select + ):not(.mat-datepicker-toggle) { + margin-right: 12px; + } + } + + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + border-radius: 0 24px 24px 0; + + > .mat-icon { + margin-left: 12px !important; + } + + > .mat-mdc-icon-button { + margin: 0 -10px 0 2px !important; + } + + > .mat-mdc-select { + margin-left: 12px !important; + } + + > .mat-datepicker-toggle { + margin-left: 4px !important; + } + + > *:not(.mat-icon):not(.mat-mdc-icon-button):not( + .mat-mdc-select + ):not(.mat-datepicker-toggle) { + margin-left: 12px !important; + } + } + } + } + } + } + + /* Dense */ + &.angor-mat-dense { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + .mat-mdc-icon-button { + width: 32px !important; + min-width: 32px; + height: 32px; + min-height: 32px; + } + } + + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix { + > .mat-mdc-icon-button { + margin-left: -6px; + margin-right: 12px; + } + } + + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + > .mat-mdc-icon-button { + margin-left: 12px; + margin-right: -6px; + } + } + + .mat-mdc-form-field-infix { + min-height: 40px; + + /* Textarea */ + textarea.mat-mdc-input-element { + margin: 8px 0; + } + } + } + } + + /* Adds better alignment for textarea inputs */ + &:has(textarea.mat-mdc-input-element) { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + padding-top: 10px !important; + } + } + } + } + + /* Rounded */ + &.angor-mat-rounded { + .mat-mdc-text-field-wrapper { + border-radius: 20px; + } + + /* Emphasized affix */ + &.angor-mat-emphasized-affix { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix { + border-radius: 20px 0 0 20px !important; + } + + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + border-radius: 0 20px 20px 0 !important; + } + } + } + } + } + } + + /* Emphasized affix */ + &.angor-mat-emphasized-affix { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix { + align-self: stretch !important; + margin: 0 16px 0 -16px !important; + padding-left: 16px !important; + border-radius: 6px 0 0 6px; + border-right-width: 1px; + border-style: solid; + + > .mat-icon { + margin-right: 16px; + } + + > .mat-mdc-icon-button { + margin: 0 6px 0 -10px !important; + } + + > .mat-mdc-select { + margin-right: 12px !important; + } + + > .mat-datepicker-toggle { + margin-right: 8px; + } + + > *:not(.mat-icon):not(.mat-mdc-icon-button):not( + .mat-mdc-select + ):not(.mat-datepicker-toggle) { + margin-right: 16px; + } + } + + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + align-self: stretch !important; + margin: 0 -16px 0 16px !important; + padding-right: 16px !important; + border-radius: 0 6px 6px 0; + border-left-width: 1px; + border-style: solid; + + > .mat-icon { + margin-left: 16px; + } + + > .mat-mdc-icon-button { + margin: 0 -10px 0 6px !important; + } + + > .mat-mdc-select { + margin: 0 -4px 0 16px !important; + } + + > .mat-datepicker-toggle { + margin-left: 8px; + } + + > *:not(.mat-icon):not(.mat-mdc-icon-button):not( + .mat-mdc-select + ):not(.mat-datepicker-toggle) { + margin-left: 16px; + } + } + + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + @apply bg-default border-gray-300 dark:border-gray-500 #{'!important'}; + } + } + } + + /* with Textarea */ + &:has(textarea.mat-mdc-input-element) { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mat-mdc-form-field-icon-prefix, + .mat-mdc-form-field-text-prefix, + .mat-mdc-form-field-icon-suffix, + .mat-mdc-form-field-text-suffix { + align-items: flex-start; + } + } + } + } + } + + /* Bolder border width */ + &.angor-mat-bold { + .mat-mdc-text-field-wrapper { + border-width: 2px !important; + } + } +} + +/* "outline" appearance */ +.mat-mdc-form-field.mat-form-field-appearance-outline { + /* Invalid */ + &.mat-form-field-invalid { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + border-color: var(--angor-warn) !important; + } + } + + /* Focused */ + &.mat-focused:not(.mat-form-field-invalid) { + /* Primary */ + &.mat-primary { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + border-color: var(--angor-primary) !important; + } + } + + /* Accent */ + &.mat-accent { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + border-color: var(--angor-accent) !important; + } + } + } + + &:not(.mat-focused):not(.mat-form-field-invalid) { + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mdc-notched-outline { + .mdc-notched-outline__leading, + .mdc-notched-outline__notch, + .mdc-notched-outline__trailing { + @apply border-slate-300 dark:border-slate-500 #{'!important'}; + } + } + } + } + } + + /* Remove the extra border on the right side of the notch */ + /* Tailwind's global border setter causes this issue */ + .mat-mdc-text-field-wrapper { + .mat-mdc-form-field-flex { + .mdc-notched-outline { + .mdc-notched-outline__notch { + border-right-style: none !important; + } + } + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Datepicker +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* @ Icon +/* -------------------------------------------------------------------------- */ +.mat-icon { + display: inline-flex !important; + align-items: center; + justify-content: center; + width: 24px; + min-width: 24px; + height: 24px; + min-height: 24px; + font-size: 24px; + line-height: 24px; + -webkit-appearance: none !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Inputs +/* -------------------------------------------------------------------------- */ +.mat-mdc-input-element { + &::placeholder { + transition: none !important; + @apply text-hint #{'!important'}; + } + + &::-moz-placeholder { + transition: none !important; + @apply text-hint #{'!important'}; + } + + &::-webkit-input-placeholder { + transition: none !important; + @apply text-hint #{'!important'}; + } + + &:-ms-input-placeholder { + transition: none !important; + @apply text-hint #{'!important'}; + } +} + +/* Invalid */ +.mat-form-field-invalid { + .mat-mdc-input-element { + /* Placeholder color */ + &::placeholder { + @apply text-warn #{'!important'}; + } + + &::-moz-placeholder { + @apply text-warn #{'!important'}; + } + + &::-webkit-input-placeholder { + @apply text-warn #{'!important'}; + } + + &:-ms-input-placeholder { + @apply text-warn #{'!important'}; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Menu +/* -------------------------------------------------------------------------- */ +.mat-mdc-menu-panel { + min-width: 144px !important; + + .mat-mdc-menu-content { + .mat-mdc-menu-item { + .mat-mdc-menu-item-text { + display: flex; + align-items: center; + padding-right: 16px; + } + + .mat-icon-no-color { + --tw-text-opacity: 1; + color: rgba(var(--angor-mat-icon-rgb), var(--tw-text-opacity)); + } + } + + /* Divider within mat-menu */ + mat-divider { + margin: 8px 0; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Paginator +/* -------------------------------------------------------------------------- */ +.mat-mdc-paginator { + .mat-mdc-paginator-container { + padding: 8px 16px; + justify-content: space-between; + + @screen sm { + justify-content: normal; + } + + /* Page size select */ + .mat-mdc-paginator-page-size { + align-items: center; + min-height: 40px; + margin: 8px; + + .mat-mdc-paginator-page-size-label { + display: none; + margin-right: 12px; + + @screen sm { + display: block; + } + } + + .mat-mdc-paginator-page-size-select { + margin: 0; + + .mat-mdc-text-field-wrapper { + padding: 0 10px; + + .mat-form-field-flex { + min-height: 32px; + } + } + } + } + + /* Range actions */ + .mat-mdc-paginator-range-actions { + margin: 8px 0; + + .mat-mdc-paginator-range-label { + margin-right: 16px; + } + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Select +/* -------------------------------------------------------------------------- */ +.mat-mdc-select { + display: inline-flex !important; + + .mat-mdc-select-placeholder { + transition: none !important; + @apply text-hint #{'!important'}; + } + + .mat-mdc-select-trigger { + .mat-mdc-select-value { + position: relative; + display: flex; + max-width: none; + + .mat-mdc-select-value-text { + display: inline-flex; + + > * { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + } + } + } + + .mat-mdc-select-arrow-wrapper { + transform: translateY(0) !important; + + .mat-mdc-select-arrow { + margin: 0 0 0 8px; + @apply text-secondary #{!important}; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Slide Toggle +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ +/* @ Snack bar +/* -------------------------------------------------------------------------- */ +.mat-mdc-snack-bar-container { + .mat-mdc-button.mat-mdc-snack-bar-action:not(:disabled) { + color: #ffffff !important; + + .dark & { + color: #000000 !important; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Stepper +/* -------------------------------------------------------------------------- */ +.mat-step-icon { + /* Do not override the mat-icon color */ + .mat-icon { + color: currentColor !important; + } +} + +.mat-step-label, +.mat-step-label-selected { + font-weight: 500 !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Table +/* -------------------------------------------------------------------------- */ +.mat-mdc-table { + .mdc-data-table__row:not(.mdc-data-table__row--selected):hover { + background: none !important; + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Tabs +/* -------------------------------------------------------------------------- */ +.mat-mdc-tab-group { + /* No header */ + &.angor-mat-no-header { + .mat-mdc-tab-header { + height: 0 !important; + max-height: 0 !important; + border: none !important; + visibility: hidden !important; + opacity: 0 !important; + } + } + + &:not(.mat-background-primary):not(.mat-background-accent) { + .mat-mdc-tab-header { + .mat-mdc-tab-label-container { + box-shadow: inset 0 -1px var(--angor-border); + } + } + } + + .mat-mdc-tab-header { + .mat-mdc-tab-label-container { + margin: 0 24px; + } + } + + .mat-mdc-tab-body-content { + padding: 24px; + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Textarea +/* -------------------------------------------------------------------------- */ +textarea.mat-mdc-input-element { + box-sizing: content-box !important; +} + +/* -------------------------------------------------------------------------- */ +/* @ Toolbar +/* -------------------------------------------------------------------------- */ +.mat-toolbar { + /* Apply primary contrast color */ + &.mat-primary { + .mat-icon { + @apply text-on-primary #{'!important'}; + } + + .text-secondary { + @apply text-on-primary text-opacity-60 #{'!important'}; + } + + .text-hint { + @apply text-on-primary text-opacity-38 #{'!important'}; + } + + .text-disabled { + @apply text-on-primary text-opacity-38 #{'!important'}; + } + + .divider { + @apply text-on-primary text-opacity-12 #{'!important'}; + } + } + + /* Apply accent contrast color */ + &.mat-accent { + .mat-icon { + @apply text-on-accent #{'!important'}; + } + + .text-secondary { + @apply text-on-accent text-opacity-60 #{'!important'}; + } + + .text-hint { + @apply text-on-accent text-opacity-38 #{'!important'}; + } + + .text-disabled { + @apply text-on-accent text-opacity-38 #{'!important'}; + } + + .divider { + @apply text-on-accent text-opacity-12 #{'!important'}; + } + } + + /* Apply warn contrast color */ + &.mat-warn { + .mat-icon { + @apply text-on-warn #{'!important'}; + } + + .text-secondary { + @apply text-on-warn text-opacity-60 #{'!important'}; + } + + .text-hint { + @apply text-on-warn text-opacity-38 #{'!important'}; + } + + .text-disabled { + @apply text-on-warn text-opacity-38 #{'!important'}; + } + + .divider { + @apply text-on-warn text-opacity-12 #{'!important'}; + } + } +} + +/* -------------------------------------------------------------------------- */ +/* @ Tooltip +/* -------------------------------------------------------------------------- */ + +.mat-mdc-tooltip .mdc-tooltip__surface { + background-color: var(--angor-text-default) !important; + color: white; + + .dark & { + background-color: var(--angor-text-secondary) !important; + color: var(--angor-bg-default) !important; + } +} diff --git a/src/@angor/styles/overrides/highlightjs.scss b/src/@angor/styles/overrides/highlightjs.scss new file mode 100644 index 0000000..bed6367 --- /dev/null +++ b/src/@angor/styles/overrides/highlightjs.scss @@ -0,0 +1,81 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Highlight.js overrides +/* ----------------------------------------------------------------------------------------------------- */ +code[class*='language-'], +pre[class*='language-'] { + .hljs-comment, + .hljs-quote { + color: #8b9fc1; + font-style: italic; + } + + .hljs-doctag, + .hljs-keyword, + .hljs-formula { + color: #22d3ee; + } + + .hljs-name { + color: #e879f9; + } + + .hljs-tag { + color: #bae6fd; + } + + .hljs-section, + .hljs-selector-tag, + .hljs-deletion, + .hljs-subst { + color: #f87f71; + } + + .hljs-literal { + color: #36beff; + } + + .hljs-string, + .hljs-regexp, + .hljs-addition, + .hljs-attribute, + .hljs-meta-string { + color: #bef264; + } + + .hljs-built_in, + .hljs-class .hljs-title { + color: #ffd374; + } + + .hljs-attr, + .hljs-variable, + .hljs-template-variable, + .hljs-type, + .hljs-selector-class, + .hljs-selector-attr, + .hljs-selector-pseudo, + .hljs-number { + color: #22d3ee; + } + + .hljs-symbol, + .hljs-bullet, + .hljs-link, + .hljs-meta, + .hljs-selector-id, + .hljs-title { + color: #e879f9; + } + + .hljs-emphasis { + font-style: italic; + } + + .hljs-strong { + font-weight: 700; + } + + .hljs-link { + text-decoration: underline; + } +} diff --git a/src/@angor/styles/overrides/perfect-scrollbar.scss b/src/@angor/styles/overrides/perfect-scrollbar.scss new file mode 100644 index 0000000..1ab1b68 --- /dev/null +++ b/src/@angor/styles/overrides/perfect-scrollbar.scss @@ -0,0 +1,68 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Perfect scrollbar overrides +/* ----------------------------------------------------------------------------------------------------- */ +.ps { + position: relative; + + &:hover, + &.ps--focus, + &.ps--scrolling-x, + &.ps--scrolling-y { + > .ps__rail-x, + > .ps__rail-y { + opacity: 1; + } + } + + > .ps__rail-x, + > .ps__rail-y { + z-index: 99999; + } + + > .ps__rail-x { + height: 14px; + background: transparent !important; + transition: none !important; + + &:hover, + &:focus, + &.ps--clicking { + opacity: 1; + + .ps__thumb-x { + height: 10px; + } + } + + .ps__thumb-x { + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15); + height: 6px; + transition: height 225ms cubic-bezier(0.25, 0.8, 0.25, 1); + } + } + + > .ps__rail-y { + width: 14px; + background: transparent !important; + transition: none !important; + left: auto !important; + + &:hover, + &:focus, + &.ps--clicking { + opacity: 1; + + .ps__thumb-y { + width: 10px; + } + } + + .ps__thumb-y { + background: rgba(0, 0, 0, 0.5); + box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.15); + width: 6px; + transition: width 225ms cubic-bezier(0.25, 0.8, 0.25, 1); + } + } +} diff --git a/src/@angor/styles/overrides/quill.scss b/src/@angor/styles/overrides/quill.scss new file mode 100644 index 0000000..7c7780b --- /dev/null +++ b/src/@angor/styles/overrides/quill.scss @@ -0,0 +1,139 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Quill editor overrides +/* ----------------------------------------------------------------------------------------------------- */ +.ql-toolbar { + border-radius: 6px 6px 0 0; + padding: 0 !important; + @apply bg-gray-100; + @apply border-gray-300 border-opacity-100 #{'!important'}; + + .dark & { + background-color: rgba(0, 0, 0, 0.05); + @apply border-gray-500 #{'!important'}; + } + + .ql-formats { + margin: 11px 8px !important; + } + + .ql-picker { + &.ql-expanded { + .ql-picker-label { + @apply border-gray-300; + + .dark & { + @apply border-gray-500; + } + } + + .ql-picker-options { + z-index: 10 !important; + @apply bg-card border-gray-300; + + .dark & { + @apply border-gray-500; + } + } + } + + .ql-picker-label { + @apply text-default; + } + + .ql-picker-options { + .ql-picker-item { + @apply text-default; + } + } + } + + .ql-stroke, + .ql-stroke-mitter { + stroke: var(--angor-icon); + } + + .ql-fill { + fill: var(--angor-icon); + } + + button:hover, + button:focus, + button.ql-active, + .ql-picker-label:hover, + .ql-picker-label.ql-active, + .ql-picker-item:hover, + .ql-picker-item.ql-selected { + @apply text-primary #{'!important'}; + + .ql-stroke, + .ql-stroke-mitter { + stroke: var(--angor-primary) !important; + } + + .ql-fill { + fill: var(--angor-primary) !important; + } + } +} + +.ql-container { + overflow: auto; + min-height: 160px; + max-height: 400px; + border-radius: 0 0 6px 6px; + @apply border-gray-300 border-opacity-100 shadow-sm #{'!important'}; + + .dark & { + @apply border-gray-500 #{'!important'}; + } + + .ql-editor { + @apply bg-card; + + .dark & { + //background-color: rgba(0, 0, 0, 0.05); + } + + &.ql-blank::before { + @apply text-hint; + } + } + + .ql-tooltip { + @apply rounded-md border-gray-300 bg-gray-100 px-3 py-1 shadow-sm; + + .dark & { + @apply border-gray-700 bg-gray-700 shadow-lg #{'!important'}; + } + + // Label + &:before { + @apply text-secondary; + } + + .ql-action, + .ql-remove { + @apply border-gray-300 text-primary; + + .dark & { + @apply border-gray-300 text-primary-400; + } + } + + .ql-action:after { + @apply border-r border-r-gray-300 #{'!important'}; + + .dark & { + @apply border-r-gray-500 #{'!important'}; + } + } + + input { + @apply text-default rounded-sm border-gray-300 bg-white #{'!important'}; + + .dark & { + @apply border-gray-500 bg-gray-700 #{'!important'}; + } + } + } +} diff --git a/src/@angor/styles/tailwind.scss b/src/@angor/styles/tailwind.scss new file mode 100644 index 0000000..8efefe9 --- /dev/null +++ b/src/@angor/styles/tailwind.scss @@ -0,0 +1,143 @@ +/* This injects Tailwind's base styles and any base styles registered by plugins. */ +@tailwind base; + +/* This injects additional styles into Tailwind's base styles layer. */ +@layer base { + * { + /* Text rendering */ + text-rendering: optimizeLegibility; + -o-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + -moz-text-rendering: optimizeLegibility; + -webkit-text-rendering: optimizeLegibility; + -webkit-tap-highlight-color: transparent; + + /* Remove the focus ring */ + &:focus { + outline: none !important; + } + } + + /* HTML and Body default styles */ + html, + body { + display: flex; + flex-direction: column; + flex: 1 1 auto; + width: 100%; + min-height: 100%; + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + } + + /* Font size */ + html { + font-size: 16px; + } + + body { + font-size: 0.875rem; + } + + /* Stylistic alternates for Inter */ + body { + font-feature-settings: 'salt'; + } + + /* Better spacing and border for horizontal rule */ + hr { + margin: 32px 0; + border-bottom-width: 1px; + } + + /* Make images and videos to take up all the available space */ + img { + width: 100%; + vertical-align: top; + } + + /* Fix: Disabled placeholder color is too faded on Safari */ + input[disabled] { + opacity: 1; + -webkit-text-fill-color: currentColor; + } + + /* Set the background and foreground colors */ + body, + .dark, + .light { + @apply text-default bg-default #{'!important'}; + } + + /* Set the border color */ + *, + ::before, + ::after { + --tw-border-opacity: 1 !important; + border-color: rgba(var(--angor-border-rgb), var(--tw-border-opacity)); + + .dark & { + --tw-border-opacity: 0.12 !important; + } + } + + /* Style scrollbars on platforms other than MacOS and iOS */ + @media only screen and (min-width: 960px) { + body:not(.os-mac) { + ::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: rgba(0, 0, 0, 0); + } + + ::-webkit-scrollbar:hover { + width: 8px; + height: 8px; + background-color: rgba(0, 0, 0, 0.06); + } + + ::-webkit-scrollbar-thumb { + border: 2px solid transparent; + border-radius: 20px; + box-shadow: inset 0 0 0 20px rgba(0, 0, 0, 0.24); + } + + ::-webkit-scrollbar-thumb:active { + border-radius: 20px; + box-shadow: inset 0 0 0 20px rgba(0, 0, 0, 0.37); + } + + &.dark { + ::-webkit-scrollbar-thumb { + box-shadow: inset 0 0 0 20px rgba(255, 255, 255, 0.24); + } + + ::-webkit-scrollbar-thumb:active { + box-shadow: inset 0 0 0 20px rgba(255, 255, 255, 0.37); + } + } + } + } + + /* Set the foreground color for disabled elements */ + [disabled] { + @apply text-disabled #{'!important'}; + } + + /* Print styles */ + @media print { + /* Make the base font size smaller for print so everything is scaled nicely */ + html { + font-size: 12px !important; + } + + body, + .dark, + .light { + background: none !important; + } + } +} + +/* This injects Tailwind's component classes and any component classes registered by plugins. */ +@tailwind components; diff --git a/src/@angor/styles/themes.scss b/src/@angor/styles/themes.scss new file mode 100644 index 0000000..24eaa12 --- /dev/null +++ b/src/@angor/styles/themes.scss @@ -0,0 +1,258 @@ +@use 'sass:map'; +@use '@angular/material' as mat; +@use 'user-themes' as userThemes; + +/* Set the base colors for light themes */ +$light-base: ( + foreground: ( + base: #000000, + divider: #9bbac3, + /* slate.200 */ dividers: #9bbac3, + /* slate.200 */ disabled: #3b7586, + /* slate.400 */ disabled-button: #3b7586, + /* slate.400 */ disabled-text: #3b7586, + /* slate.400 */ elevation: #000000, + hint-text: #3b7586, + /* slate.400 */ secondary-text: #083b46, + /* slate.500 */ icon: #083b46, + /* slate.500 */ icons: #083b46, + /* slate.500 */ mat-icon: #083b46, + /* slate.500 */ text: #032128, + /* slate.800 */ slider-min: #032128, + /* slate.800 */ slider-off: #6b98a4, + /* slate.300 */ slider-off-active: #3b7586 /* slate.400 */, + ), + background: ( + status-bar: #6b98a4, + /* slate.300 */ app-bar: #ffffff, + background: #cbdde1, + /* slate.100 */ hover: rgba(59, 117, 134, 0.12), + /* slate.400 + opacity */ card: #ffffff, + dialog: #ffffff, + disabled-button: rgba(59, 117, 134, 0.38), + /* slate.400 + opacity */ raised-button: #ffffff, + focused-button: #083b46, + /* slate.500 */ selected-button: #9bbac3, + /* slate.200 */ selected-disabled-button: #9bbac3, + /* slate.200 */ disabled-button-toggle: #6b98a4, + /* slate.300 */ unselected-chip: #9bbac3, + /* slate.200 */ disabled-list-option: #6b98a4, + /* slate.300 */ tooltip: #032128 /* slate.800 */, + ), +); + +/* Set the base colors for dark themes */ +$dark-base: ( + foreground: ( + base: #ffffff, + divider: rgba(203, 221, 225, 0.12), + /* slate.100 + opacity */ dividers: rgba(203, 221, 225, 0.12), + /* slate.100 + opacity */ disabled: #07343e, + /* slate.600 */ disabled-button: #032128, + /* slate.800 */ disabled-text: #07343e, + /* slate.600 */ elevation: #000000, + hint-text: #083b46, + /* slate.500 */ secondary-text: #3b7586, + /* slate.400 */ icon: #cbdde1, + /* slate.100 */ icons: #cbdde1, + /* slate.100 */ mat-icon: #3b7586, + /* slate.400 */ text: #ffffff, + slider-min: #ffffff, + slider-off: #083b46, + /* slate.500 */ slider-off-active: #3b7586 /* slate.400 */, + ), + background: ( + status-bar: #022229, + /* slate.900 */ app-bar: #022229, + /* slate.900 */ background: #022229, + /* slate.900 */ hover: rgba(255, 255, 255, 0.05), + card: #032128, + /* slate.800 */ dialog: #032128, + /* slate.800 */ disabled-button: rgba(2, 34, 41, 0.38), + /* slate.900 + opacity */ raised-button: #022229, + /* slate.900 */ focused-button: #9bbac3, + /* slate.200 */ selected-button: rgba(255, 255, 255, 0.05), + selected-disabled-button: #032128, + /* slate.800 */ disabled-button-toggle: #022229, + /* slate.900 */ unselected-chip: #07343e, + /* slate.600 */ disabled-list-option: #9bbac3, + /* slate.200 */ tooltip: #083b46 /* slate.500 */, + ), +); + + +/* Include the core Angular Material styles */ +@include mat.core(); + +/* Create a base theme without any color to set the density and typography */ +@include mat.all-component-themes( + ( + color: null, + density: 0, + typography: + mat.m2-define-typography-config( + $font-family: theme('fontFamily.sans'), + $headline-1: + mat.m2-define-typography-level( + 1.875rem, + 2.25rem, + 800, + theme('fontFamily.sans') + ), + $headline-2: + mat.m2-define-typography-level( + 1.25rem, + 1.75rem, + 700, + theme('fontFamily.sans') + ), + $headline-3: + mat.m2-define-typography-level( + 1.125rem, + 1.75rem, + 600, + theme('fontFamily.sans') + ), + $headline-4: + mat.m2-define-typography-level( + 0.875rem, + 1.25rem, + 600, + theme('fontFamily.sans') + ), + $headline-5: + mat.m2-define-typography-level( + 0.875rem, + 1.5rem, + 400, + theme('fontFamily.sans') + ), + $headline-6: + mat.m2-define-typography-level( + 0.875rem, + 1.5rem, + 400, + theme('fontFamily.sans') + ), + $subtitle-1: + mat.m2-define-typography-level( + 1rem, + 1.75rem, + 400, + theme('fontFamily.sans') + ), + $subtitle-2: + mat.m2-define-typography-level( + 0.875rem, + 1.25rem, + 600, + theme('fontFamily.sans') + ), + $body-1: + mat.m2-define-typography-level( + 0.875rem, + 1.5rem, + 400, + theme('fontFamily.sans') + ), + $body-2: + mat.m2-define-typography-level( + 0.875rem, + 1.5rem, + 400, + theme('fontFamily.sans') + ), + $caption: + mat.m2-define-typography-level( + 0.75rem, + 1rem, + 400, + theme('fontFamily.sans') + ), + $button: + mat.m2-define-typography-level( + 0.875rem, + 0.875rem, + 500, + theme('fontFamily.sans') + ), + $overline: + mat.m2-define-typography-level( + 0.75rem, + 2rem, + 500, + theme('fontFamily.sans') + ) + ), + ) +); + +/* Loop through user themes and generate Angular Material themes */ +@each $name, $theme in userThemes.$user-themes { + /* Generate the palettes */ + $palettes: (); + @each $name in (primary, accent, warn) { + /* Define the Angular Material theme */ + $palette: mat.m2-define-palette(map.get($theme, $name)); + + /* Replace the default colors on the defined Material palette */ + $palette: map.merge( + $palette, + ( + default: map.get(map.get($theme, $name), DEFAULT), + lighter: map.get(map.get($theme, $name), 100), + darker: map.get(map.get($theme, $name), 700), + text: map.get(map.get($theme, $name), DEFAULT), + default-contrast: + map.get(map.get(map.get($theme, $name), contrast), DEFAULT), + lighter-contrast: + map.get(map.get(map.get($theme, $name), contrast), 100), + darker-contrast: + map.get(map.get(map.get($theme, $name), contrast), 700), + ) + ); + + $palettes: map.merge($palettes, (#{$name}: $palette)); + } + + /* Define a light & dark Angular Material theme with the generated palettes */ + $light-theme: mat.m2-define-light-theme( + ( + color: $palettes, + ) + ); + + $dark-theme: mat.m2-define-dark-theme( + ( + color: $palettes, + ) + ); + + /* Merge the custom base colors with the generated themes */ + $light-theme-colors: map.merge(map.get($light-theme, color), $light-base); + $light-theme: map.merge( + ( + color: $light-theme-colors, + ), + $light-theme-colors + ); + + $dark-theme-colors: map.merge(map.get($dark-theme, color), $dark-base); + $dark-theme: map.merge( + ( + color: $dark-theme-colors, + ), + $dark-theme-colors + ); + + /* Generate and encapsulate Angular Material themes */ + #{map.get($theme, selector)} .light, + #{map.get($theme, selector)}.light { + @include mat.all-component-colors($light-theme); + } + + #{map.get($theme, selector)} .dark, + #{map.get($theme, selector)}.dark { + @include mat.all-component-colors($dark-theme); + } +} diff --git a/src/@angor/styles/user-themes.scss b/src/@angor/styles/user-themes.scss new file mode 100644 index 0000000..33be077 --- /dev/null +++ b/src/@angor/styles/user-themes.scss @@ -0,0 +1 @@ +$user-themes: (default: (selector: ".theme-default", primary: (50: #eef2ff, 100: #e0e7ff, 200: #c7d2fe, 300: #a5b4fc, 400: #818cf8, 500: #6366f1, 600: #4f46e5, 700: #4338ca, 800: #3730a3, 900: #312e81, 950: #1e1b4b, DEFAULT: #4f46e5, contrast: (50: #1e1b4b, 100: #1e1b4b, 200: #1e1b4b, 300: #1e1b4b, 400: #1e1b4b, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF))), brand: (selector: ".theme-brand", primary: (50: #eff2f3, 100: #e1e7ea, 200: #c3d0d4, 300: #9fb5bd, 400: #7195a2, 500: #086c81, 600: #076275, 700: #065768, 800: #064a58, 900: #043a45, DEFAULT: #086c81, contrast: (50: #043a45, 100: #043a45, 200: #043a45, 300: #043a45, 400: #043a45, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, DEFAULT: #FFFFFF)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF))), teal: (selector: ".theme-teal", primary: (50: #f0fdfa, 100: #ccfbf1, 200: #99f6e4, 300: #5eead4, 400: #2dd4bf, 500: #14b8a6, 600: #0d9488, 700: #0f766e, 800: #115e59, 900: #134e4a, 950: #042f2e, DEFAULT: #0d9488, contrast: (50: #042f2e, 100: #042f2e, 200: #042f2e, 300: #042f2e, 400: #042f2e, 500: #042f2e, 600: #042f2e, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #042f2e)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF))), rose: (selector: ".theme-rose", primary: (50: #fff1f2, 100: #ffe4e6, 200: #fecdd3, 300: #fda4af, 400: #fb7185, 500: #f43f5e, 600: #e11d48, 700: #be123c, 800: #9f1239, 900: #881337, 950: #4c0519, DEFAULT: #f43f5e, contrast: (50: #4c0519, 100: #4c0519, 200: #4c0519, 300: #4c0519, 400: #4c0519, 500: #4c0519, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #4c0519)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF))), purple: (selector: ".theme-purple", primary: (50: #faf5ff, 100: #f3e8ff, 200: #e9d5ff, 300: #d8b4fe, 400: #c084fc, 500: #a855f7, 600: #9333ea, 700: #7e22ce, 800: #6b21a8, 900: #581c87, 950: #3b0764, DEFAULT: #9333ea, contrast: (50: #3b0764, 100: #3b0764, 200: #3b0764, 300: #3b0764, 400: #3b0764, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF))), amber: (selector: ".theme-amber", primary: (50: #fffbeb, 100: #fef3c7, 200: #fde68a, 300: #fcd34d, 400: #fbbf24, 500: #f59e0b, 600: #d97706, 700: #b45309, 800: #92400e, 900: #78350f, 950: #451a03, DEFAULT: #f59e0b, contrast: (50: #451a03, 100: #451a03, 200: #451a03, 300: #451a03, 400: #451a03, 500: #451a03, 600: #451a03, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #451a03)), accent: (50: #f8fafc, 100: #f1f5f9, 200: #e2e8f0, 300: #cbd5e1, 400: #94a3b8, 500: #64748b, 600: #475569, 700: #334155, 800: #1e293b, 900: #0f172a, 950: #020617, DEFAULT: #1e293b, contrast: (50: #020617, 100: #020617, 200: #020617, 300: #020617, 400: #020617, 500: #FFFFFF, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)), warn: (50: #fef2f2, 100: #fee2e2, 200: #fecaca, 300: #fca5a5, 400: #f87171, 500: #ef4444, 600: #dc2626, 700: #b91c1c, 800: #991b1b, 900: #7f1d1d, 950: #450a0a, DEFAULT: #dc2626, contrast: (50: #450a0a, 100: #450a0a, 200: #450a0a, 300: #450a0a, 400: #450a0a, 500: #fef2f2, 600: #FFFFFF, 700: #FFFFFF, 800: #FFFFFF, 900: #FFFFFF, 950: #FFFFFF, DEFAULT: #FFFFFF)))); \ No newline at end of file diff --git a/src/@angor/tailwind/plugins/icon-size.js b/src/@angor/tailwind/plugins/icon-size.js new file mode 100644 index 0000000..bb9f61b --- /dev/null +++ b/src/@angor/tailwind/plugins/icon-size.js @@ -0,0 +1,47 @@ +const plugin = require('tailwindcss/plugin'); + +module.exports = plugin( + ({ matchUtilities, theme }) => { + matchUtilities( + { + 'icon-size': (value) => ({ + width: value, + height: value, + minWidth: value, + minHeight: value, + fontSize: value, + lineHeight: value, + [`svg`]: { + width: value, + height: value, + }, + }), + }, + { + values: theme('iconSize'), + } + ); + }, + { + theme: { + iconSize: { + 3: '0.75rem', + 3.5: '0.875rem', + 4: '1rem', + 4.5: '1.125rem', + 5: '1.25rem', + 6: '1.5rem', + 7: '1.75rem', + 8: '2rem', + 10: '2.5rem', + 12: '3rem', + 14: '3.5rem', + 16: '4rem', + 18: '4.5rem', + 20: '5rem', + 22: '5.5rem', + 24: '6rem', + }, + }, + } +); diff --git a/src/@angor/tailwind/plugins/theming.js b/src/@angor/tailwind/plugins/theming.js new file mode 100644 index 0000000..fd1d743 --- /dev/null +++ b/src/@angor/tailwind/plugins/theming.js @@ -0,0 +1,407 @@ +const chroma = require('chroma-js'); +const _ = require('lodash'); +const fs = require('fs'); +const path = require('path'); +const colors = require('tailwindcss/colors'); +const plugin = require('tailwindcss/plugin'); +const flattenColorPalette = + require('tailwindcss/lib/util/flattenColorPalette').default; +const generateContrasts = require( + path.resolve(__dirname, '../utils/generate-contrasts') +); +const jsonToSassMap = require( + path.resolve(__dirname, '../utils/json-to-sass-map') +); + +// ----------------------------------------------------------------------------------------------------- +// @ Utilities +// ----------------------------------------------------------------------------------------------------- + +/** + * Normalizes the provided theme by omitting empty values and values that + * start with "on" from each palette. Also sets the correct DEFAULT value + * of each palette. + * + * @param theme + */ +const normalizeTheme = (theme) => { + return _.fromPairs( + _.map( + _.omitBy( + theme, + (palette, paletteName) => + paletteName.startsWith('on') || _.isEmpty(palette) + ), + (palette, paletteName) => [ + paletteName, + { + ...palette, + DEFAULT: palette['DEFAULT'] || palette[500], + }, + ] + ) + ); +}; + +// ----------------------------------------------------------------------------------------------------- +// @ ANGOR TailwindCSS Main Plugin +// ----------------------------------------------------------------------------------------------------- +const theming = plugin.withOptions( + (options) => + ({ addComponents, e, theme }) => { + /** + * Create user themes object by going through the provided themes and + * merging them with the provided "default" so, we can have a complete + * set of color palettes for each user theme. + */ + const userThemes = _.fromPairs( + _.map(options.themes, (theme, themeName) => [ + themeName, + _.defaults({}, theme, options.themes['default']), + ]) + ); + + /** + * Normalize the themes and assign it to the themes object. This will + * be the final object that we create a SASS map from + */ + let themes = _.fromPairs( + _.map(userThemes, (theme, themeName) => [ + themeName, + normalizeTheme(theme), + ]) + ); + + /** + * Go through the themes to generate the contrasts and filter the + * palettes to only have "primary", "accent" and "warn" objects. + */ + themes = _.fromPairs( + _.map(themes, (theme, themeName) => [ + themeName, + _.pick( + _.fromPairs( + _.map(theme, (palette, paletteName) => [ + paletteName, + { + ...palette, + contrast: _.fromPairs( + _.map( + generateContrasts(palette), + (color, hue) => [ + hue, + _.get(userThemes[themeName], [ + `on-${paletteName}`, + hue, + ]) || color, + ] + ) + ), + }, + ]) + ), + ['primary', 'accent', 'warn'] + ), + ]) + ); + + /** + * Go through the themes and attach appropriate class selectors so, + * we can use them to encapsulate each theme. + */ + themes = _.fromPairs( + _.map(themes, (theme, themeName) => [ + themeName, + { + selector: `".theme-${themeName}"`, + ...theme, + }, + ]) + ); + + /* Generate the SASS map using the themes object */ + const sassMap = jsonToSassMap( + JSON.stringify({ 'user-themes': themes }) + ); + + /* Get the file path */ + const filename = path.resolve( + __dirname, + '../../styles/user-themes.scss' + ); + + /* Read the file and get its data */ + let data; + try { + data = fs.readFileSync(filename, { encoding: 'utf8' }); + } catch (err) { + console.error(err); + } + + /* Write the file if the map has been changed */ + if (data !== sassMap) { + try { + fs.writeFileSync(filename, sassMap, { encoding: 'utf8' }); + } catch (err) { + console.error(err); + } + } + + /** + * Iterate through the user's themes and build Tailwind components containing + * CSS Custom Properties using the colors from them. This allows switching + * themes by simply replacing a class name as well as nesting them. + */ + addComponents( + _.fromPairs( + _.map(options.themes, (theme, themeName) => [ + themeName === 'default' + ? 'body, .theme-default' + : `.theme-${e(themeName)}`, + _.fromPairs( + _.flatten( + _.map( + flattenColorPalette( + _.fromPairs( + _.flatten( + _.map( + normalizeTheme(theme), + (palette, paletteName) => [ + [ + e(paletteName), + palette, + ], + [ + `on-${e(paletteName)}`, + _.fromPairs( + _.map( + generateContrasts( + palette + ), + ( + color, + hue + ) => [ + hue, + _.get( + theme, + [ + `on-${paletteName}`, + hue, + ] + ) || + color, + ] + ) + ), + ], + ] + ) + ) + ) + ), + (value, key) => [ + [`--angor-${e(key)}`, value], + [ + `--angor-${e(key)}-rgb`, + chroma(value).rgb().join(','), + ], + ] + ) + ) + ), + ]) + ) + ); + + /** + * Generate scheme based css custom properties and utility classes + */ + const schemeCustomProps = _.map( + ['light', 'dark'], + (colorScheme) => { + const isDark = colorScheme === 'dark'; + const background = theme( + `angor.customProps.background.${colorScheme}` + ); + const foreground = theme( + `angor.customProps.foreground.${colorScheme}` + ); + const lightSchemeSelectors = + 'body.light, .light, .dark .light'; + const darkSchemeSelectors = + 'body.dark, .dark, .light .dark'; + + return { + [isDark ? darkSchemeSelectors : lightSchemeSelectors]: { + /** + * If a custom property is not available, browsers will use + * the fallback value. In this case, we want to use '--is-dark' + * as the indicator of a dark theme so, we can use it like this: + * background-color: var(--is-dark, red); + * + * If we set '--is-dark' as "true" on dark themes, the above rule + * won't work because of the said "fallback value" logic. Therefore, + * we set the '--is-dark' to "false" on light themes and not set it + * at all on dark themes so that the fallback value can be used on + * dark themes. + * + * On light themes, since '--is-dark' exists, the above rule will be + * interpolated as: + * "background-color: false" + * + * On dark themes, since '--is-dark' doesn't exist, the fallback value + * will be used ('red' in this case) and the rule will be interpolated as: + * "background-color: red" + * + * It's easier to understand and remember like this. + */ + ...(!isDark ? { '--is-dark': 'false' } : {}), + + /* Generate custom properties from customProps */ + ..._.fromPairs( + _.flatten( + _.map(background, (value, key) => [ + [`--angor-${e(key)}`, value], + [ + `--angor-${e(key)}-rgb`, + chroma(value).rgb().join(','), + ], + ]) + ) + ), + ..._.fromPairs( + _.flatten( + _.map(foreground, (value, key) => [ + [`--angor-${e(key)}`, value], + [ + `--angor-${e(key)}-rgb`, + chroma(value).rgb().join(','), + ], + ]) + ) + ), + }, + }; + } + ); + + const schemeUtilities = (() => { + /* Generate general styles & utilities */ + return {}; + })(); + + addComponents(schemeCustomProps); + addComponents(schemeUtilities); + }, + (options) => { + return { + theme: { + extend: { + /** + * Add 'Primary', 'Accent' and 'Warn' palettes as colors so all color utilities + * are generated for them; "bg-primary", "text-on-primary", "bg-accent-600" etc. + * This will also allow using arbitrary values with them such as opacity and such. + */ + colors: _.fromPairs( + _.flatten( + _.map( + _.keys( + flattenColorPalette( + normalizeTheme(options.themes.default) + ) + ), + (name) => [ + [ + name, + `rgba(var(--angor-${name}-rgb), )`, + ], + [ + `on-${name}`, + `rgba(var(--angor-on-${name}-rgb), )`, + ], + ] + ) + ) + ), + }, + angor: { + customProps: { + background: { + light: { + 'bg-app-bar': '#FFFFFF', + 'bg-card': '#FFFFFF', + 'bg-default': '#cbdde1', + /* slate.100 */ + 'bg-dialog': '#FFFFFF', + 'bg-hover': chroma('#3b7586') + /* slate.400 */ + .alpha(0.12) + .css(), + 'bg-status-bar': '#6b98a4', + /* slate.300 */ + }, + dark: { + 'bg-app-bar': '#022229', + /* slate.900 */ + 'bg-card': '#042f38', + /* slate.800 */ + 'bg-default': '#022229', + /* slate.900 */ + 'bg-dialog': '#032128', + /* slate.800 */ + 'bg-hover': 'rgba(255, 255, 255, 0.05)', + 'bg-status-bar': '#022229', + /* slate.900 */ + }, + }, + foreground: { + light: { + 'text-default': '#032128', + /* slate.800 */ + 'text-secondary': '#155b6a', + /* slate.500 */ + 'text-hint': '#3b7586', + /* slate.400 */ + 'text-disabled': '#3b7586', + /* slate.400 */ + border: '#9bbac3', + /* slate.200 */ + divider: '#9bbac3', + /* slate.200 */ + icon: '#083b46', + /* slate.500 */ + 'mat-icon': '#083b46', + /* slate.500 */ + }, + dark: { + 'text-default': '#FFFFFF', + 'text-secondary': '#3b7586', + /* slate.400 */ + 'text-hint': '#40899f', + /* slate.500 */ + 'text-disabled': '#07343e', + /* slate.600 */ + border: chroma('#cbdde1') + /* slate.100 */ + .alpha(0.12) + .css(), + divider: chroma('#cbdde1') + /* slate.100 */ + .alpha(0.12) + .css(), + icon: '#3b7586', + /* slate.400 */ + 'mat-icon': '#3b7586', + /* slate.400 */ + }, + }, + }, + }, + + }, + }; + } +); + +module.exports = theming; diff --git a/src/@angor/tailwind/plugins/utilities.js b/src/@angor/tailwind/plugins/utilities.js new file mode 100644 index 0000000..ccad64c --- /dev/null +++ b/src/@angor/tailwind/plugins/utilities.js @@ -0,0 +1,65 @@ +const plugin = require('tailwindcss/plugin'); + +module.exports = plugin(({ addComponents }) => { + /* + * Add base components. These are very important for everything to look + * correct. We are adding these to the 'components' layer because they must + * be defined before pretty much everything else. + */ + addComponents({ + '.mat-icon': { + '--tw-text-opacity': '1', + color: 'rgba(var(--angor-mat-icon-rgb), var(--tw-text-opacity))', + }, + '.text-default': { + '--tw-text-opacity': '1 !important', + color: 'rgba(var(--angor-text-default-rgb), var(--tw-text-opacity)) !important', + }, + '.text-secondary': { + '--tw-text-opacity': '1 !important', + color: 'rgba(var(--angor-text-secondary-rgb), var(--tw-text-opacity)) !important', + }, + '.text-hint': { + '--tw-text-opacity': '1 !important', + color: 'rgba(var(--angor-text-hint-rgb), var(--tw-text-opacity)) !important', + }, + '.text-disabled': { + '--tw-text-opacity': '1 !important', + color: 'rgba(var(--angor-text-disabled-rgb), var(--tw-text-opacity)) !important', + }, + '.divider': { + color: 'var(--angor-divider) !important', + }, + '.bg-card': { + '--tw-bg-opacity': '1 !important', + backgroundColor: + 'rgba(var(--angor-bg-card-rgb), var(--tw-bg-opacity)) !important', + }, + '.bg-default': { + '--tw-bg-opacity': '1 !important', + backgroundColor: + 'rgba(var(--angor-bg-default-rgb), var(--tw-bg-opacity)) !important', + }, + '.bg-dialog': { + '--tw-bg-opacity': '1 !important', + backgroundColor: + 'rgba(var(--angor-bg-dialog-rgb), var(--tw-bg-opacity)) !important', + }, + '.ring-bg-default': { + '--tw-ring-opacity': '1 !important', + '--tw-ring-color': + 'rgba(var(--angor-bg-default-rgb), var(--tw-ring-opacity)) !important', + }, + '.ring-bg-card': { + '--tw-ring-opacity': '1 !important', + '--tw-ring-color': + 'rgba(var(--angor-bg-card-rgb), var(--tw-ring-opacity)) !important', + }, + }); + + addComponents({ + '.bg-hover': { + backgroundColor: 'var(--angor-bg-hover) !important', + }, + }); +}); diff --git a/src/@angor/tailwind/utils/generate-contrasts.js b/src/@angor/tailwind/utils/generate-contrasts.js new file mode 100644 index 0000000..2a121cc --- /dev/null +++ b/src/@angor/tailwind/utils/generate-contrasts.js @@ -0,0 +1,37 @@ +const chroma = require('chroma-js'); +const _ = require('lodash'); + +/** + * Generates contrasting counterparts of the given palette. + * The provided palette must be in the same format with + * default Tailwind color palettes. + * + * @param palette + * @private + */ +const generateContrasts = (palette) => { + const lightColor = '#FFFFFF'; + let darkColor = '#FFFFFF'; + + // Iterate through the palette to find the darkest color + _.forEach(palette, (color) => { + darkColor = + chroma.contrast(color, '#FFFFFF') > + chroma.contrast(darkColor, '#FFFFFF') + ? color + : darkColor; + }); + + // Generate the contrasting colors + return _.fromPairs( + _.map(palette, (color, hue) => [ + hue, + chroma.contrast(color, darkColor) > + chroma.contrast(color, lightColor) + ? darkColor + : lightColor, + ]) + ); +}; + +module.exports = generateContrasts; diff --git a/src/@angor/tailwind/utils/generate-palette.js b/src/@angor/tailwind/utils/generate-palette.js new file mode 100644 index 0000000..0147e44 --- /dev/null +++ b/src/@angor/tailwind/utils/generate-palette.js @@ -0,0 +1,100 @@ +const chroma = require('chroma-js'); +const _ = require('lodash'); + +/** + * Generates palettes from the provided configuration. + * Accepts a single color string or a Tailwind-like + * color object. If provided Tailwind-like color object, + * it must have a 500 hue level. + * + * @param config + */ +const generatePalette = (config) => { + // Prepare an empty palette + const palette = { + 50: null, + 100: null, + 200: null, + 300: null, + 400: null, + 500: null, + 600: null, + 700: null, + 800: null, + 900: null, + }; + + // If a single color is provided, + // assign it to the 500 + if (_.isString(config)) { + palette[500] = chroma.valid(config) ? config : null; + } + + // If a partial palette is provided, + // assign the values + if (_.isPlainObject(config)) { + if (!chroma.valid(config[500])) { + throw new Error( + 'You must have a 500 hue in your palette configuration! Make sure the main color of your palette is marked as 500.' + ); + } + + // Remove everything that is not a hue/color entry + config = _.pick(config, Object.keys(palette)); + + // Merge the values + _.mergeWith(palette, config, (objValue, srcValue) => + chroma.valid(srcValue) ? srcValue : null + ); + } + + // Prepare the colors array + const colors = Object.values(palette).filter((color) => color); + + // Generate a very dark and a very light versions of the + // default color to use them as the boundary colors rather + // than using pure white and pure black. This will stop + // in between colors' hue values to slipping into the grays. + colors.unshift( + chroma + .scale(['white', palette[500]]) + .domain([0, 1]) + .mode('lrgb') + .colors(50)[1] + ); + colors.push( + chroma + .scale(['black', palette[500]]) + .domain([0, 1]) + .mode('lrgb') + .colors(10)[1] + ); + + // Prepare the domains array + const domain = [ + 0, + ...Object.entries(palette) + .filter(([key, value]) => value) + .map(([key]) => parseInt(key) / 1000), + 1, + ]; + + // Generate the color scale + const scale = chroma.scale(colors).domain(domain).mode('lrgb'); + + // Build and return the final palette + return { + 50: scale(0.05).hex(), + 100: scale(0.1).hex(), + 200: scale(0.2).hex(), + 300: scale(0.3).hex(), + 400: scale(0.4).hex(), + 500: scale(0.5).hex(), + 600: scale(0.6).hex(), + 700: scale(0.7).hex(), + 800: scale(0.8).hex(), + 900: scale(0.9).hex(), + }; +}; + +module.exports = generatePalette; diff --git a/src/@angor/tailwind/utils/json-to-sass-map.js b/src/@angor/tailwind/utils/json-to-sass-map.js new file mode 100644 index 0000000..c19736f --- /dev/null +++ b/src/@angor/tailwind/utils/json-to-sass-map.js @@ -0,0 +1,49 @@ +const _ = require('lodash'); + +module.exports = (data) => { + if (!data) { + return; + } + + data = JSON.parse(data); + + const getSCSS = (chunk) => { + let scss = ''; + + if (typeof chunk === 'object' && !Array.isArray(chunk)) { + _.mapKeys(chunk, (value, key) => { + scss += key + ': '; + + if (typeof value === 'object') { + if (Array.isArray(value)) { + scss += '('; + _.each(value, (val1) => { + if (Array.isArray(val1)) { + _.each(val1, (val2) => { + scss += val2 + ' '; + }); + scss = scss.slice(0, -1) + ', '; + } else { + scss += val1 + ', '; + } + }); + scss = scss.slice(0, -2); + scss += ')'; + } else { + scss += '(' + getSCSS(value) + ')'; + } + } else { + scss += getSCSS(value); + } + scss += ', '; + }); + scss = scss.slice(0, -2); + } else { + scss += chunk; + } + + return scss; + }; + + return '$' + getSCSS(data) + ';'; +}; diff --git a/src/@angor/validators/index.ts b/src/@angor/validators/index.ts new file mode 100644 index 0000000..d21c6d8 --- /dev/null +++ b/src/@angor/validators/index.ts @@ -0,0 +1 @@ +export * from '@angor/validators/public-api'; diff --git a/src/@angor/validators/public-api.ts b/src/@angor/validators/public-api.ts new file mode 100644 index 0000000..bed1859 --- /dev/null +++ b/src/@angor/validators/public-api.ts @@ -0,0 +1 @@ +export * from '@angor/validators/validators'; diff --git a/src/@angor/validators/validators.ts b/src/@angor/validators/validators.ts new file mode 100644 index 0000000..849176d --- /dev/null +++ b/src/@angor/validators/validators.ts @@ -0,0 +1,58 @@ +import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms'; + +export class AngorValidators { + /** + * Check for empty (optional fields) values + * + * @param value + */ + static isEmptyInputValue(value: any): boolean { + return value == null || value.length === 0; + } + + /** + * Must match validator + * + * @param controlPath A dot-delimited string values that define the path to the control. + * @param matchingControlPath A dot-delimited string values that define the path to the matching control. + */ + static mustMatch( + controlPath: string, + matchingControlPath: string + ): ValidatorFn { + return (formGroup: AbstractControl): ValidationErrors | null => { + // Get the control and matching control + const control = formGroup.get(controlPath); + const matchingControl = formGroup.get(matchingControlPath); + + // Return if control or matching control doesn't exist + if (!control || !matchingControl) { + return null; + } + + // Delete the mustMatch error to reset the error on the matching control + if (matchingControl.hasError('mustMatch')) { + delete matchingControl.errors.mustMatch; + matchingControl.updateValueAndValidity(); + } + + // Don't validate empty values on the matching control + // Don't validate if values are matching + if ( + this.isEmptyInputValue(matchingControl.value) || + control.value === matchingControl.value + ) { + return null; + } + + // Prepare the validation errors + const errors = { mustMatch: true }; + + // Set the validation error on the matching control + matchingControl.setErrors(errors); + + // Return the errors + return errors; + }; + } +} diff --git a/src/@angor/version/angor-version.ts b/src/@angor/version/angor-version.ts new file mode 100644 index 0000000..89da1a6 --- /dev/null +++ b/src/@angor/version/angor-version.ts @@ -0,0 +1,3 @@ +import { Version } from '@angor/version/version'; + +export const ANGOR_VERSION = new Version('0.0.1').full; diff --git a/src/@angor/version/index.ts b/src/@angor/version/index.ts new file mode 100644 index 0000000..485e298 --- /dev/null +++ b/src/@angor/version/index.ts @@ -0,0 +1 @@ +export * from '@angor/version/public-api'; diff --git a/src/@angor/version/public-api.ts b/src/@angor/version/public-api.ts new file mode 100644 index 0000000..e19b26b --- /dev/null +++ b/src/@angor/version/public-api.ts @@ -0,0 +1,2 @@ +export * from '@angor/version/angor-version'; +export * from '@angor/version/version'; diff --git a/src/@angor/version/version.ts b/src/@angor/version/version.ts new file mode 100644 index 0000000..99a9f93 --- /dev/null +++ b/src/@angor/version/version.ts @@ -0,0 +1,19 @@ +/** + * Derived from Angular's version class + */ +export class Version { + public readonly full: string; + public readonly major: string; + public readonly minor: string; + public readonly patch: string; + + /** + * Constructor + */ + constructor(public version: string) { + this.full = version; + this.major = version.split('.')[0]; + this.minor = version.split('.')[1]; + this.patch = version.split('.').slice(2).join('.'); + } +} diff --git a/src/app/app.component.html b/src/app/app.component.html new file mode 100644 index 0000000..0680b43 --- /dev/null +++ b/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/src/app/app.component.scss b/src/app/app.component.scss new file mode 100644 index 0000000..a8b9eb0 --- /dev/null +++ b/src/app/app.component.scss @@ -0,0 +1,6 @@ +:host { + display: flex; + flex: 1 1 auto; // Flex-grow, flex-shrink, and basis set to auto + width: 100%; // Full width of the container + height: 100%; // Full height of the container +} diff --git a/src/app/app.component.ts b/src/app/app.component.ts new file mode 100644 index 0000000..2217019 --- /dev/null +++ b/src/app/app.component.ts @@ -0,0 +1,18 @@ +import { Component } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.scss'], + standalone: true, + imports: [RouterOutlet], +}) +export class AppComponent { + /** + * Constructor for AppComponent + */ + constructor() { + // Initialization logic if needed + } +} diff --git a/src/app/app.config.ts b/src/app/app.config.ts new file mode 100644 index 0000000..41f0d2c --- /dev/null +++ b/src/app/app.config.ts @@ -0,0 +1,112 @@ +import { provideHttpClient } from '@angular/common/http'; +import { APP_INITIALIZER, ApplicationConfig, inject } from '@angular/core'; +import { LuxonDateAdapter } from '@angular/material-luxon-adapter'; +import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { + PreloadAllModules, + provideRouter, + withInMemoryScrolling, + withPreloading, +} from '@angular/router'; +import { provideAngor } from '@angor'; +import { TranslocoService, provideTransloco } from '@ngneat/transloco'; +import { appRoutes } from 'app/app.routes'; +import { provideAuth } from 'app/core/auth/auth.provider'; +import { provideIcons } from 'app/core/icons/icons.provider'; +import { mockApiServices } from 'app/mock-api'; +import { firstValueFrom } from 'rxjs'; +import { TranslocoHttpLoader } from './core/transloco/transloco.http-loader'; + +/** + * Application configuration and providers + */ +export const appConfig: ApplicationConfig = { + providers: [ + provideAnimations(), + provideHttpClient(), + provideRouter( + appRoutes, + withPreloading(PreloadAllModules), + withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }) + ), + + // Material Date Adapter Configuration + { + provide: DateAdapter, + useClass: LuxonDateAdapter, + }, + { + provide: MAT_DATE_FORMATS, + useValue: { + parse: { + dateInput: 'D', // Date format for parsing + }, + display: { + dateInput: 'DDD', // Date format for input display + monthYearLabel: 'LLL yyyy', // Format for month-year labels + dateA11yLabel: 'DD', // Accessible format for dates + monthYearA11yLabel: 'LLLL yyyy', // Accessible format for month-year + }, + }, + }, + + // Transloco Configuration + provideTransloco({ + config: { + availableLangs: [ + { + id: 'en', + label: 'English', + } + ], + defaultLang: 'en', + fallbackLang: 'en', + reRenderOnLangChange: true, + prodMode: true, + }, + loader: TranslocoHttpLoader, + }), + { + // Preload default language before app starts to prevent content issues + provide: APP_INITIALIZER, + useFactory: () => { + const translocoService = inject(TranslocoService); + const defaultLang = translocoService.getDefaultLang(); + translocoService.setActiveLang(defaultLang); + + return () => firstValueFrom(translocoService.load(defaultLang)); + }, + multi: true, + }, + + // Angor Configuration + provideAuth(), + provideIcons(), + provideAngor({ + mockApi: { + delay: 0, + services: mockApiServices, + }, + angor: JSON.parse(localStorage.getItem('angorConfig')) ?? { + layout: 'classic', + scheme: 'light', + screens: { + sm: '600px', + md: '960px', + lg: '1280px', + xl: '1440px', + }, + theme: 'theme-brand', + themes: [ + { id: 'theme-brand', name: 'Brand' }, + { id: 'theme-default', name: 'Default' }, + { id: 'theme-teal', name: 'Teal' }, + { id: 'theme-rose', name: 'Rose' }, + { id: 'theme-purple', name: 'Purple' }, + { id: 'theme-amber', name: 'Amber' }, + ], + }, + }), + ], +}; diff --git a/src/app/app.resolvers.ts b/src/app/app.resolvers.ts new file mode 100644 index 0000000..f435a2e --- /dev/null +++ b/src/app/app.resolvers.ts @@ -0,0 +1,22 @@ +import { inject } from '@angular/core'; +import { NavigationService } from 'app/core/navigation/navigation.service'; +import { NotificationsService } from 'app/layout/common/notifications/notifications.service'; +import { QuickChatService } from 'app/layout/common/quick-chat/quick-chat.service'; +import { forkJoin } from 'rxjs'; + +/** + * Resolver function to initialize application data + * It makes multiple API calls and waits for all to finish + */ +export const initialDataResolver = () => { + const navigationService = inject(NavigationService); + const notificationsService = inject(NotificationsService); + const quickChatService = inject(QuickChatService); + + // Combine API calls into a single observable + return forkJoin([ + navigationService.get(), // Fetch navigation data + notificationsService.getAll(), // Fetch all notifications + quickChatService.getChats(), // Fetch chat data + ]); +}; diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts new file mode 100644 index 0000000..28a9295 --- /dev/null +++ b/src/app/app.routes.ts @@ -0,0 +1,117 @@ +import { Route } from '@angular/router'; +import { initialDataResolver } from 'app/app.resolvers'; +import { AuthGuard } from 'app/core/auth/guards/auth.guard'; +import { NoAuthGuard } from 'app/core/auth/guards/noAuth.guard'; +import { LayoutComponent } from 'app/layout/layout.component'; + +/** + * Application routes configuration + */ +export const appRoutes: Route[] = [ + + // Redirect root path to '/explore' + { + path: '', + pathMatch: 'full', + redirectTo: 'explore' + }, + + // Redirect signed-in user to '/explore' + { + path: 'signed-in-redirect', + pathMatch: 'full', + redirectTo: 'explore' + }, + + // Routes for guests + { + path: '', + canActivate: [NoAuthGuard], + canActivateChild: [NoAuthGuard], + component: LayoutComponent, + data: { layout: 'empty' }, + children: [ + { + path: 'confirmation-required', + loadChildren: () => import('app/components/auth/confirmation-required/confirmation-required.routes') + }, + { + path: 'forgot-password', + loadChildren: () => import('app/components/auth/forgot-password/forgot-password.routes') + }, + { + path: 'reset-password', + loadChildren: () => import('app/components/auth/reset-password/reset-password.routes') + }, + { + path: 'sign-in', + loadChildren: () => import('app/components/auth/sign-in/sign-in.routes') + }, + { + path: 'sign-up', + loadChildren: () => import('app/components/auth/sign-up/sign-up.routes') + } + ] + }, + + // Routes for authenticated users + { + path: '', + canActivate: [AuthGuard], + canActivateChild: [AuthGuard], + component: LayoutComponent, + data: { layout: 'empty' }, + children: [ + { + path: 'sign-out', + loadChildren: () => import('app/components/auth/sign-out/sign-out.routes') + }, + { + path: 'unlock-session', + loadChildren: () => import('app/components/auth/unlock-session/unlock-session.routes') + } + ] + }, + + + + // Authenticated routes for Angor + { + path: '', + canActivate: [AuthGuard], + canActivateChild: [AuthGuard], + component: LayoutComponent, + resolve: { initialData: initialDataResolver }, + children: [ + { + path: 'home', + loadChildren: () => import('app/components/home/home.routes') + }, + { + path: 'explore', + loadChildren: () => import('app/components/explore/explore.routes') + }, + { + path: 'profile', + loadChildren: () => import('app/components/profile/profile.routes') + }, + { + path: 'settings', + loadChildren: () => import('app/components/settings/settings.routes') + }, + { + path: 'chat', + loadChildren: () => import('app/components/chat/chat.routes') + }, + { + path: '404-not-found', + pathMatch: 'full', + loadChildren: () => import('app/components/pages/error/error-404/error-404.routes') + }, + { + path: '**', + redirectTo: '404-not-found' + } + ] + } +]; diff --git a/src/app/components/auth/confirmation-required/confirmation-required.component.html b/src/app/components/auth/confirmation-required/confirmation-required.component.html new file mode 100644 index 0000000..4fa6fe5 --- /dev/null +++ b/src/app/components/auth/confirmation-required/confirmation-required.component.html @@ -0,0 +1,96 @@ +
+
+
+ +
+ +
+ + +
+ Confirmation required +
+
+ A confirmation mail with instructions has been sent to your + email address. Follow those instructions to confirm your email + address and activate your account. +
+ + +
+ Return to + sign in + +
+
+
+ +
diff --git a/src/app/components/auth/confirmation-required/confirmation-required.component.ts b/src/app/components/auth/confirmation-required/confirmation-required.component.ts new file mode 100644 index 0000000..916de77 --- /dev/null +++ b/src/app/components/auth/confirmation-required/confirmation-required.component.ts @@ -0,0 +1,18 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; + +@Component({ + selector: 'auth-confirmation-required', + templateUrl: './confirmation-required.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [RouterLink], +}) +export class AuthConfirmationRequiredComponent { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/auth/confirmation-required/confirmation-required.routes.ts b/src/app/components/auth/confirmation-required/confirmation-required.routes.ts new file mode 100644 index 0000000..495eaec --- /dev/null +++ b/src/app/components/auth/confirmation-required/confirmation-required.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthConfirmationRequiredComponent } from 'app/components/auth/confirmation-required/confirmation-required.component'; + +export default [ + { + path: '', + component: AuthConfirmationRequiredComponent, + }, +] as Routes; diff --git a/src/app/components/auth/forgot-password/forgot-password.component.html b/src/app/components/auth/forgot-password/forgot-password.component.html new file mode 100644 index 0000000..5d9e289 --- /dev/null +++ b/src/app/components/auth/forgot-password/forgot-password.component.html @@ -0,0 +1,147 @@ +
+
+
+ +
+ +
+ + +
+ Forgot password? +
+
+ Fill the form to reset your password +
+ + + @if (showAlert) { + + {{ alert.message }} + + } + + +
+ + + Email address + + @if (forgotPasswordForm.get('email').hasError('required')) { + Email address is required + } + @if (forgotPasswordForm.get('email').hasError('email')) { + + Please enter a valid email address + + } + + + + + + +
+ Return to + sign in + +
+
+
+
+ +
diff --git a/src/app/components/auth/forgot-password/forgot-password.component.ts b/src/app/components/auth/forgot-password/forgot-password.component.ts new file mode 100644 index 0000000..f3dd7f2 --- /dev/null +++ b/src/app/components/auth/forgot-password/forgot-password.component.ts @@ -0,0 +1,122 @@ +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormsModule, + NgForm, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { AuthService } from 'app/core/auth/auth.service'; +import { finalize } from 'rxjs'; + +@Component({ + selector: 'auth-forgot-password', + templateUrl: './forgot-password.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [ + AngorAlertComponent, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatProgressSpinnerModule, + RouterLink, + ], +}) +export class AuthForgotPasswordComponent implements OnInit { + @ViewChild('forgotPasswordNgForm') forgotPasswordNgForm: NgForm; + + alert: { type: AngorAlertType; message: string } = { + type: 'success', + message: '', + }; + forgotPasswordForm: UntypedFormGroup; + showAlert: boolean = false; + + /** + * Constructor + */ + constructor( + private _authService: AuthService, + private _formBuilder: UntypedFormBuilder + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.forgotPasswordForm = this._formBuilder.group({ + email: ['', [Validators.required, Validators.email]], + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Send the reset link + */ + sendResetLink(): void { + // Return if the form is invalid + if (this.forgotPasswordForm.invalid) { + return; + } + + // Disable the form + this.forgotPasswordForm.disable(); + + // Hide the alert + this.showAlert = false; + + // Forgot password + this._authService + .forgotPassword(this.forgotPasswordForm.get('email').value) + .pipe( + finalize(() => { + // Re-enable the form + this.forgotPasswordForm.enable(); + + // Reset the form + this.forgotPasswordNgForm.resetForm(); + + // Show the alert + this.showAlert = true; + }) + ) + .subscribe( + (response) => { + // Set the alert + this.alert = { + type: 'success', + message: + "Password reset sent! You'll receive an email if you are registered on our system.", + }; + }, + (response) => { + // Set the alert + this.alert = { + type: 'error', + message: + 'Email does not found! Are you sure you are already a member?', + }; + } + ); + } +} diff --git a/src/app/components/auth/forgot-password/forgot-password.routes.ts b/src/app/components/auth/forgot-password/forgot-password.routes.ts new file mode 100644 index 0000000..8d2e41d --- /dev/null +++ b/src/app/components/auth/forgot-password/forgot-password.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthForgotPasswordComponent } from 'app/components/auth/forgot-password/forgot-password.component'; + +export default [ + { + path: '', + component: AuthForgotPasswordComponent, + }, +] as Routes; diff --git a/src/app/components/auth/reset-password/reset-password.component.html b/src/app/components/auth/reset-password/reset-password.component.html new file mode 100644 index 0000000..8f80437 --- /dev/null +++ b/src/app/components/auth/reset-password/reset-password.component.html @@ -0,0 +1,220 @@ +
+
+
+ +
+ +
+ + +
+ Reset your password +
+
+ Create a new password for your account +
+ + + @if (showAlert) { + + {{ alert.message }} + + } + + +
+ + + Password + + + Password is required + + + + + Password (Confirm) + + + @if ( + resetPasswordForm + .get('passwordConfirm') + .hasError('required') + ) { + + Password confirmation is required + + } + @if ( + resetPasswordForm + .get('passwordConfirm') + .hasError('mustMatch') + ) { + Passwords must match + } + + + + + + +
+ Return to + sign in + +
+
+
+
+ +
diff --git a/src/app/components/auth/reset-password/reset-password.component.ts b/src/app/components/auth/reset-password/reset-password.component.ts new file mode 100644 index 0000000..0620d27 --- /dev/null +++ b/src/app/components/auth/reset-password/reset-password.component.ts @@ -0,0 +1,132 @@ +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormsModule, + NgForm, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { AngorValidators } from '@angor/validators'; +import { AuthService } from 'app/core/auth/auth.service'; +import { finalize } from 'rxjs'; + +@Component({ + selector: 'auth-reset-password', + templateUrl: './reset-password.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [ + AngorAlertComponent, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatProgressSpinnerModule, + RouterLink, + ], +}) +export class AuthResetPasswordComponent implements OnInit { + @ViewChild('resetPasswordNgForm') resetPasswordNgForm: NgForm; + + alert: { type: AngorAlertType; message: string } = { + type: 'success', + message: '', + }; + resetPasswordForm: UntypedFormGroup; + showAlert: boolean = false; + + /** + * Constructor + */ + constructor( + private _authService: AuthService, + private _formBuilder: UntypedFormBuilder + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.resetPasswordForm = this._formBuilder.group( + { + password: ['', Validators.required], + passwordConfirm: ['', Validators.required], + }, + { + validators: AngorValidators.mustMatch( + 'password', + 'passwordConfirm' + ), + } + ); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Reset password + */ + resetPassword(): void { + // Return if the form is invalid + if (this.resetPasswordForm.invalid) { + return; + } + + // Disable the form + this.resetPasswordForm.disable(); + + // Hide the alert + this.showAlert = false; + + // Send the request to the server + this._authService + .resetPassword(this.resetPasswordForm.get('password').value) + .pipe( + finalize(() => { + // Re-enable the form + this.resetPasswordForm.enable(); + + // Reset the form + this.resetPasswordNgForm.resetForm(); + + // Show the alert + this.showAlert = true; + }) + ) + .subscribe( + (response) => { + // Set the alert + this.alert = { + type: 'success', + message: 'Your password has been reset.', + }; + }, + (response) => { + // Set the alert + this.alert = { + type: 'error', + message: 'Something went wrong, please try again.', + }; + } + ); + } +} diff --git a/src/app/components/auth/reset-password/reset-password.routes.ts b/src/app/components/auth/reset-password/reset-password.routes.ts new file mode 100644 index 0000000..f1760a5 --- /dev/null +++ b/src/app/components/auth/reset-password/reset-password.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthResetPasswordComponent } from 'app/components/auth/reset-password/reset-password.component'; + +export default [ + { + path: '', + component: AuthResetPasswordComponent, + }, +] as Routes; diff --git a/src/app/components/auth/sign-in/sign-in.component.html b/src/app/components/auth/sign-in/sign-in.component.html new file mode 100644 index 0000000..bae9f02 --- /dev/null +++ b/src/app/components/auth/sign-in/sign-in.component.html @@ -0,0 +1,232 @@ +
+
+
+ +
+ +
+ + +
+ Sign in +
+
+
Don't have an account?
+ Sign up + +
+ + + + You are browsing Angor Demo. Click on the "Sign + in" button to access the Demo and Documentation. + + + + @if (showAlert) { + + {{ alert.message }} + + } + + +
+ + + Email address + + @if (signInForm.get('email').hasError('required')) { + Email address is required + } + @if (signInForm.get('email').hasError('email')) { + + Please enter a valid email address + + } + + + + + Password + + + Password is required + + + +
+ + Remember me + + Forgot password? + +
+ + + + + +
+
+
Or continue with
+
+
+ + +
+ + + +
+
+
+
+ +
diff --git a/src/app/components/auth/sign-in/sign-in.component.ts b/src/app/components/auth/sign-in/sign-in.component.ts new file mode 100644 index 0000000..02fbdda --- /dev/null +++ b/src/app/components/auth/sign-in/sign-in.component.ts @@ -0,0 +1,131 @@ +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormsModule, + NgForm, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { AuthService } from 'app/core/auth/auth.service'; + +@Component({ + selector: 'auth-sign-in', + templateUrl: './sign-in.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [ + RouterLink, + AngorAlertComponent, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatCheckboxModule, + MatProgressSpinnerModule, + ], +}) +export class AuthSignInComponent implements OnInit { + @ViewChild('signInNgForm') signInNgForm: NgForm; + + alert: { type: AngorAlertType; message: string } = { + type: 'success', + message: '', + }; + signInForm: UntypedFormGroup; + showAlert: boolean = false; + + /** + * Constructor + */ + constructor( + private _activatedRoute: ActivatedRoute, + private _authService: AuthService, + private _formBuilder: UntypedFormBuilder, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.signInForm = this._formBuilder.group({ + email: [ + 'username@angor.io', + [Validators.required, Validators.email], + ], + password: ['admin', Validators.required], + rememberMe: [''], + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Sign in + */ + signIn(): void { + // Return if the form is invalid + if (this.signInForm.invalid) { + return; + } + + // Disable the form + this.signInForm.disable(); + + // Hide the alert + this.showAlert = false; + + // Sign in + this._authService.signIn(this.signInForm.value).subscribe( + () => { + // Set the redirect url. + // The '/signed-in-redirect' is a dummy url to catch the request and redirect the user + // to the correct page after a successful sign in. This way, that url can be set via + // routing file and we don't have to touch here. + const redirectURL = + this._activatedRoute.snapshot.queryParamMap.get( + 'redirectURL' + ) || '/signed-in-redirect'; + + // Navigate to the redirect url + this._router.navigateByUrl(redirectURL); + }, + (response) => { + // Re-enable the form + this.signInForm.enable(); + + // Reset the form + this.signInNgForm.resetForm(); + + // Set the alert + this.alert = { + type: 'error', + message: 'Wrong email or password', + }; + + // Show the alert + this.showAlert = true; + } + ); + } +} diff --git a/src/app/components/auth/sign-in/sign-in.routes.ts b/src/app/components/auth/sign-in/sign-in.routes.ts new file mode 100644 index 0000000..37707aa --- /dev/null +++ b/src/app/components/auth/sign-in/sign-in.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthSignInComponent } from 'app/components/auth/sign-in/sign-in.component'; + +export default [ + { + path: '', + component: AuthSignInComponent, + }, +] as Routes; diff --git a/src/app/components/auth/sign-out/sign-out.component.html b/src/app/components/auth/sign-out/sign-out.component.html new file mode 100644 index 0000000..647d627 --- /dev/null +++ b/src/app/components/auth/sign-out/sign-out.component.html @@ -0,0 +1,41 @@ +
+
+
+ +
+ +
+ + +
+ You have signed out! +
+
+ + @if (countdown > 0) { + Redirecting in + {{ countdown | i18nPlural: countdownMapping }} + } + + + @if (countdown === 0) { + You are now being redirected! + } +
+ + +
+ Go to + sign in + +
+
+
+
diff --git a/src/app/components/auth/sign-out/sign-out.component.ts b/src/app/components/auth/sign-out/sign-out.component.ts new file mode 100644 index 0000000..e465d3e --- /dev/null +++ b/src/app/components/auth/sign-out/sign-out.component.ts @@ -0,0 +1,62 @@ +import { I18nPluralPipe } from '@angular/common'; +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { Router, RouterLink } from '@angular/router'; +import { AuthService } from 'app/core/auth/auth.service'; +import { Subject, finalize, takeUntil, takeWhile, tap, timer } from 'rxjs'; + +@Component({ + selector: 'auth-sign-out', + templateUrl: './sign-out.component.html', + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [RouterLink, I18nPluralPipe], +}) +export class AuthSignOutComponent implements OnInit, OnDestroy { + countdown: number = 5; + countdownMapping: any = { + '=1': '# second', + other: '# seconds', + }; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _authService: AuthService, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Sign out + this._authService.signOut(); + + // Redirect after the countdown + timer(1000, 1000) + .pipe( + finalize(() => { + this._router.navigate(['sign-in']); + }), + takeWhile(() => this.countdown > 0), + takeUntil(this._unsubscribeAll), + tap(() => this.countdown--) + ) + .subscribe(); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/app/components/auth/sign-out/sign-out.routes.ts b/src/app/components/auth/sign-out/sign-out.routes.ts new file mode 100644 index 0000000..e8b451b --- /dev/null +++ b/src/app/components/auth/sign-out/sign-out.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthSignOutComponent } from 'app/components/auth/sign-out/sign-out.component'; + +export default [ + { + path: '', + component: AuthSignOutComponent, + }, +] as Routes; diff --git a/src/app/components/auth/sign-up/sign-up.component.html b/src/app/components/auth/sign-up/sign-up.component.html new file mode 100644 index 0000000..0c312e7 --- /dev/null +++ b/src/app/components/auth/sign-up/sign-up.component.html @@ -0,0 +1,215 @@ +
+
+
+ +
+ +
+ + +
+ Sign up +
+
+
Already have an account?
+ Sign in + +
+ + + @if (showAlert) { + + {{ alert.message }} + + } + + +
+ + + Full name + + @if (signUpForm.get('name').hasError('required')) { + Full name is required + } + + + + + Email address + + @if (signUpForm.get('email').hasError('required')) { + Email address is required + } + @if (signUpForm.get('email').hasError('email')) { + + Please enter a valid email address + + } + + + + + Password + + + Password is required + + + + + Company + + + + +
+ + I agree with + Terms + + and + Privacy Policy + + +
+ + + +
+
+
+ +
diff --git a/src/app/components/auth/sign-up/sign-up.component.ts b/src/app/components/auth/sign-up/sign-up.component.ts new file mode 100644 index 0000000..485d420 --- /dev/null +++ b/src/app/components/auth/sign-up/sign-up.component.ts @@ -0,0 +1,120 @@ +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormsModule, + NgForm, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { Router, RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { AuthService } from 'app/core/auth/auth.service'; + +@Component({ + selector: 'auth-sign-up', + templateUrl: './sign-up.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [ + RouterLink, + AngorAlertComponent, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatCheckboxModule, + MatProgressSpinnerModule, + ], +}) +export class AuthSignUpComponent implements OnInit { + @ViewChild('signUpNgForm') signUpNgForm: NgForm; + + alert: { type: AngorAlertType; message: string } = { + type: 'success', + message: '', + }; + signUpForm: UntypedFormGroup; + showAlert: boolean = false; + + /** + * Constructor + */ + constructor( + private _authService: AuthService, + private _formBuilder: UntypedFormBuilder, + private _router: Router + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.signUpForm = this._formBuilder.group({ + name: ['', Validators.required], + email: ['', [Validators.required, Validators.email]], + password: ['', Validators.required], + company: [''], + agreements: ['', Validators.requiredTrue], + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Sign up + */ + signUp(): void { + // Do nothing if the form is invalid + if (this.signUpForm.invalid) { + return; + } + + // Disable the form + this.signUpForm.disable(); + + // Hide the alert + this.showAlert = false; + + // Sign up + this._authService.signUp(this.signUpForm.value).subscribe( + (response) => { + // Navigate to the confirmation required page + this._router.navigateByUrl('/confirmation-required'); + }, + (response) => { + // Re-enable the form + this.signUpForm.enable(); + + // Reset the form + this.signUpNgForm.resetForm(); + + // Set the alert + this.alert = { + type: 'error', + message: 'Something went wrong, please try again.', + }; + + // Show the alert + this.showAlert = true; + } + ); + } +} diff --git a/src/app/components/auth/sign-up/sign-up.routes.ts b/src/app/components/auth/sign-up/sign-up.routes.ts new file mode 100644 index 0000000..6b88af8 --- /dev/null +++ b/src/app/components/auth/sign-up/sign-up.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthSignUpComponent } from 'app/components/auth/sign-up/sign-up.component'; + +export default [ + { + path: '', + component: AuthSignUpComponent, + }, +] as Routes; diff --git a/src/app/components/auth/unlock-session/unlock-session.component.html b/src/app/components/auth/unlock-session/unlock-session.component.html new file mode 100644 index 0000000..ce475cb --- /dev/null +++ b/src/app/components/auth/unlock-session/unlock-session.component.html @@ -0,0 +1,175 @@ +
+
+
+ +
+ +
+ + +
+ Unlock your session +
+
+ Your session is locked due to inactivity +
+ + + @if (showAlert) { + + {{ alert.message }} + + } + + +
+ + + Full name + + + + + + Password + + + Password is required + + + + + + +
+ I'm not + {{ name }} +
+
+
+
+ +
diff --git a/src/app/components/auth/unlock-session/unlock-session.component.ts b/src/app/components/auth/unlock-session/unlock-session.component.ts new file mode 100644 index 0000000..a463cb3 --- /dev/null +++ b/src/app/components/auth/unlock-session/unlock-session.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { + FormsModule, + NgForm, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations'; +import { AngorAlertComponent, AngorAlertType } from '@angor/components/alert'; +import { AuthService } from 'app/core/auth/auth.service'; +import { UserService } from 'app/core/user/user.service'; + +@Component({ + selector: 'auth-unlock-session', + templateUrl: './unlock-session.component.html', + encapsulation: ViewEncapsulation.None, + animations: angorAnimations, + standalone: true, + imports: [ + AngorAlertComponent, + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatInputModule, + MatButtonModule, + MatIconModule, + MatProgressSpinnerModule, + RouterLink, + ], +}) +export class AuthUnlockSessionComponent implements OnInit { + @ViewChild('unlockSessionNgForm') unlockSessionNgForm: NgForm; + + alert: { type: AngorAlertType; message: string } = { + type: 'success', + message: '', + }; + name: string; + showAlert: boolean = false; + unlockSessionForm: UntypedFormGroup; + private _email: string; + + /** + * Constructor + */ + constructor( + private _activatedRoute: ActivatedRoute, + private _authService: AuthService, + private _formBuilder: UntypedFormBuilder, + private _router: Router, + private _userService: UserService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Get the user's name + this._userService.user$.subscribe((user) => { + this.name = user.name; + this._email = user.email; + }); + + // Create the form + this.unlockSessionForm = this._formBuilder.group({ + name: [ + { + value: this.name, + disabled: true, + }, + ], + password: ['', Validators.required], + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Unlock + */ + unlock(): void { + // Return if the form is invalid + if (this.unlockSessionForm.invalid) { + return; + } + + // Disable the form + this.unlockSessionForm.disable(); + + // Hide the alert + this.showAlert = false; + + this._authService + .unlockSession({ + email: this._email ?? '', + password: this.unlockSessionForm.get('password').value, + }) + .subscribe( + () => { + // Set the redirect url. + // The '/signed-in-redirect' is a dummy url to catch the request and redirect the user + // to the correct page after a successful sign in. This way, that url can be set via + // routing file and we don't have to touch here. + const redirectURL = + this._activatedRoute.snapshot.queryParamMap.get( + 'redirectURL' + ) || '/signed-in-redirect'; + + // Navigate to the redirect url + this._router.navigateByUrl(redirectURL); + }, + (response) => { + // Re-enable the form + this.unlockSessionForm.enable(); + + // Reset the form + this.unlockSessionNgForm.resetForm({ + name: { + value: this.name, + disabled: true, + }, + }); + + // Set the alert + this.alert = { + type: 'error', + message: 'Invalid password', + }; + + // Show the alert + this.showAlert = true; + } + ); + } +} diff --git a/src/app/components/auth/unlock-session/unlock-session.routes.ts b/src/app/components/auth/unlock-session/unlock-session.routes.ts new file mode 100644 index 0000000..4fc927e --- /dev/null +++ b/src/app/components/auth/unlock-session/unlock-session.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { AuthUnlockSessionComponent } from 'app/components/auth/unlock-session/unlock-session.component'; + +export default [ + { + path: '', + component: AuthUnlockSessionComponent, + }, +] as Routes; diff --git a/src/app/components/chat/chat.component.html b/src/app/components/chat/chat.component.html new file mode 100644 index 0000000..9c7107f --- /dev/null +++ b/src/app/components/chat/chat.component.html @@ -0,0 +1,6 @@ +
+ +
+ +
+
diff --git a/src/app/components/chat/chat.component.ts b/src/app/components/chat/chat.component.ts new file mode 100644 index 0000000..13e9117 --- /dev/null +++ b/src/app/components/chat/chat.component.ts @@ -0,0 +1,21 @@ +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { RouterOutlet } from '@angular/router'; + +@Component({ + selector: 'chat', + templateUrl: './chat.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [RouterOutlet], +}) +export class ChatComponent { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/chat/chat.routes.ts b/src/app/components/chat/chat.routes.ts new file mode 100644 index 0000000..ad18aca --- /dev/null +++ b/src/app/components/chat/chat.routes.ts @@ -0,0 +1,76 @@ +import { inject } from '@angular/core'; +import { + ActivatedRouteSnapshot, + Router, + RouterStateSnapshot, + Routes, +} from '@angular/router'; +import { ChatComponent } from 'app/components/chat/chat.component'; +import { ChatService } from 'app/components/chat/chat.service'; +import { ChatsComponent } from 'app/components/chat/chats/chats.component'; +import { ConversationComponent } from 'app/components/chat/conversation/conversation.component'; +import { EmptyConversationComponent } from 'app/components/chat/empty-conversation/empty-conversation.component'; +import { catchError, throwError } from 'rxjs'; + +/** + * Conversation resolver + * + * @param route + * @param state + */ +const conversationResolver = ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot +) => { + const chatService = inject(ChatService); + const router = inject(Router); + + return chatService.getChatById(route.paramMap.get('id')).pipe( + // Error here means the requested chat is not available + catchError((error) => { + // Log the error + console.error(error); + + // Get the parent url + const parentUrl = state.url.split('/').slice(0, -1).join('/'); + + // Navigate to there + router.navigateByUrl(parentUrl); + + // Throw an error + return throwError(error); + }) + ); +}; + +export default [ + { + path: '', + component: ChatComponent, + resolve: { + chats: () => inject(ChatService).getChats(), + contacts: () => inject(ChatService).getContacts(), + profile: () => inject(ChatService).getProfile(), + }, + children: [ + { + path: '', + component: ChatsComponent, + children: [ + { + path: '', + pathMatch: 'full', + component: EmptyConversationComponent, + }, + { + path: ':id', + component: ConversationComponent, + resolve: { + conversation: conversationResolver, + }, + }, + ], + }, + ], + }, +] as Routes; diff --git a/src/app/components/chat/chat.service.ts b/src/app/components/chat/chat.service.ts new file mode 100644 index 0000000..e5737f5 --- /dev/null +++ b/src/app/components/chat/chat.service.ts @@ -0,0 +1,203 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Chat, Contact, Profile } from 'app/components/chat/chat.types'; +import { + BehaviorSubject, + Observable, + filter, + map, + of, + switchMap, + take, + tap, + throwError, +} from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class ChatService { + private _chat: BehaviorSubject = new BehaviorSubject(null); + private _chats: BehaviorSubject = new BehaviorSubject(null); + private _contact: BehaviorSubject = new BehaviorSubject(null); + private _contacts: BehaviorSubject = new BehaviorSubject(null); + private _profile: BehaviorSubject = new BehaviorSubject(null); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for chat + */ + get chat$(): Observable { + return this._chat.asObservable(); + } + + /** + * Getter for chats + */ + get chats$(): Observable { + return this._chats.asObservable(); + } + + /** + * Getter for contact + */ + get contact$(): Observable { + return this._contact.asObservable(); + } + + /** + * Getter for contacts + */ + get contacts$(): Observable { + return this._contacts.asObservable(); + } + + /** + * Getter for profile + */ + get profile$(): Observable { + return this._profile.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get chats + */ + getChats(): Observable { + return this._httpClient.get('api/apps/chat/chats').pipe( + tap((response: Chat[]) => { + this._chats.next(response); + }) + ); + } + + /** + * Get contact + * + * @param id + */ + getContact(id: string): Observable { + return this._httpClient + .get('api/apps/chat/contacts', { params: { id } }) + .pipe( + tap((response: Contact) => { + this._contact.next(response); + }) + ); + } + + /** + * Get contacts + */ + getContacts(): Observable { + return this._httpClient.get('api/apps/chat/contacts').pipe( + tap((response: Contact[]) => { + this._contacts.next(response); + }) + ); + } + + /** + * Get profile + */ + getProfile(): Observable { + return this._httpClient.get('api/apps/chat/profile').pipe( + tap((response: Profile) => { + this._profile.next(response); + }) + ); + } + + /** + * Get chat + * + * @param id + */ + getChatById(id: string): Observable { + return this._httpClient + .get('api/apps/chat/chat', { params: { id } }) + .pipe( + map((chat) => { + // Update the chat + this._chat.next(chat); + + // Return the chat + return chat; + }), + switchMap((chat) => { + if (!chat) { + return throwError( + 'Could not found chat with id of ' + id + '!' + ); + } + + return of(chat); + }) + ); + } + + /** + * Update chat + * + * @param id + * @param chat + */ + updateChat(id: string, chat: Chat): Observable { + return this.chats$.pipe( + take(1), + switchMap((chats) => + this._httpClient + .patch('api/apps/chat/chat', { + id, + chat, + }) + .pipe( + map((updatedChat) => { + // Find the index of the updated chat + const index = chats.findIndex( + (item) => item.id === id + ); + + // Update the chat + chats[index] = updatedChat; + + // Update the chats + this._chats.next(chats); + + // Return the updated contact + return updatedChat; + }), + switchMap((updatedChat) => + this.chat$.pipe( + take(1), + filter((item) => item && item.id === id), + tap(() => { + // Update the chat if it's selected + this._chat.next(updatedChat); + + // Return the updated chat + return updatedChat; + }) + ) + ) + ) + ) + ); + } + + /** + * Reset the selected chat + */ + resetChat(): void { + this._chat.next(null); + } +} diff --git a/src/app/components/chat/chat.types.ts b/src/app/components/chat/chat.types.ts new file mode 100644 index 0000000..898642c --- /dev/null +++ b/src/app/components/chat/chat.types.ts @@ -0,0 +1,52 @@ +export interface Profile { + id?: string; + name?: string; + email?: string; + avatar?: string; + about?: string; +} + +export interface Contact { + id?: string; + avatar?: string; + name?: string; + about?: string; + details?: { + emails?: { + email?: string; + label?: string; + }[]; + phoneNumbers?: { + country?: string; + phoneNumber?: string; + label?: string; + }[]; + title?: string; + company?: string; + birthday?: string; + address?: string; + }; + attachments?: { + media?: any[]; + docs?: any[]; + links?: any[]; + }; +} + +export interface Chat { + id?: string; + contactId?: string; + contact?: Contact; + unreadCount?: number; + muted?: boolean; + lastMessage?: string; + lastMessageAt?: string; + messages?: { + id?: string; + chatId?: string; + contactId?: string; + isMine?: boolean; + value?: string; + createdAt?: string; + }[]; +} diff --git a/src/app/components/chat/chats/chats.component.html b/src/app/components/chat/chats/chats.component.html new file mode 100644 index 0000000..d67685c --- /dev/null +++ b/src/app/components/chat/chats/chats.component.html @@ -0,0 +1,292 @@ +
+ + + + + @if (drawerComponent === 'new-chat') { + + } + + + @if (drawerComponent === 'profile') { + + } + + + + + + @if (chats && chats.length > 0) { +
+ +
+
+
+
+ @if (profile.avatar) { + Profile avatar + } + @if (!profile.avatar) { +
+ {{ profile.name.charAt(0) }} +
+ } +
+
+ {{ profile.name }} +
+
+ + + + + + + + + +
+ +
+ + + + +
+
+ + + +
+ } @else { +
+ +
+ No chats +
+
+ } + + + + + @if (chats && chats.length > 0) { +
+ +
+ } +
+
+
diff --git a/src/app/components/chat/chats/chats.component.ts b/src/app/components/chat/chats/chats.component.ts new file mode 100644 index 0000000..42aa744 --- /dev/null +++ b/src/app/components/chat/chats/chats.component.ts @@ -0,0 +1,163 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { RouterLink, RouterOutlet } from '@angular/router'; +import { ChatService } from 'app/components/chat/chat.service'; +import { Chat, Profile } from 'app/components/chat/chat.types'; +import { NewChatComponent } from 'app/components/chat/new-chat/new-chat.component'; +import { ProfileComponent } from 'app/components/chat/profile/profile.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'chat-chats', + templateUrl: './chats.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatSidenavModule, + NewChatComponent, + ProfileComponent, + MatButtonModule, + MatIconModule, + MatMenuModule, + MatFormFieldModule, + MatInputModule, + NgClass, + RouterLink, + RouterOutlet, + ], +}) +export class ChatsComponent implements OnInit, OnDestroy { + chats: Chat[]; + drawerComponent: 'profile' | 'new-chat'; + drawerOpened: boolean = false; + filteredChats: Chat[]; + profile: Profile; + selectedChat: Chat; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _chatService: ChatService, + private _changeDetectorRef: ChangeDetectorRef + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Chats + this._chatService.chats$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chats: Chat[]) => { + this.chats = this.filteredChats = chats; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Profile + this._chatService.profile$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((profile: Profile) => { + this.profile = profile; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Selected chat + this._chatService.chat$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chat: Chat) => { + this.selectedChat = chat; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + + // Reset the chat + this._chatService.resetChat(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Filter the chats + * + * @param query + */ + filterChats(query: string): void { + // Reset the filter + if (!query) { + this.filteredChats = this.chats; + return; + } + + this.filteredChats = this.chats.filter((chat) => + chat.contact.name.toLowerCase().includes(query.toLowerCase()) + ); + } + + /** + * Open the new chat sidebar + */ + openNewChat(): void { + this.drawerComponent = 'new-chat'; + this.drawerOpened = true; + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Open the profile sidebar + */ + openProfile(): void { + this.drawerComponent = 'profile'; + this.drawerOpened = true; + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/chat/contact-info/contact-info.component.html b/src/app/components/chat/contact-info/contact-info.component.html new file mode 100644 index 0000000..8a5f2e1 --- /dev/null +++ b/src/app/components/chat/contact-info/contact-info.component.html @@ -0,0 +1,95 @@ +
+ +
+ +
Contact info
+
+ +
+ +
+
+ @if (chat.contact.avatar) { + + } + @if (!chat.contact.avatar) { +
+ {{ chat.contact.name.charAt(0) }} +
+ } +
+
{{ chat.contact.name }}
+
+ {{ chat.contact.about }} +
+
+ +
+ +
Media
+
+ @for (media of chat.contact.attachments.media; track media) { + + } +
+ +
+
Details
+ @if (chat.contact.details.emails.length) { +
+
Email
+
+ {{ chat.contact.details.emails[0].email }} +
+
+ } + @if (chat.contact.details.phoneNumbers.length) { +
+
+ Phone number +
+
+ {{ + chat.contact.details.phoneNumbers[0].phoneNumber + }} +
+
+ } + @if (chat.contact.details.title) { +
+
Title
+
{{ chat.contact.details.title }}
+
+ } + @if (chat.contact.details.company) { +
+
Company
+
{{ chat.contact.details.company }}
+
+ } + @if (chat.contact.details.birthday) { +
+
Birthday
+
{{ chat.contact.details.birthday }}
+
+ } + @if (chat.contact.details.address) { +
+
Address
+
{{ chat.contact.details.address }}
+
+ } +
+
+
+
diff --git a/src/app/components/chat/contact-info/contact-info.component.ts b/src/app/components/chat/contact-info/contact-info.component.ts new file mode 100644 index 0000000..8bb5509 --- /dev/null +++ b/src/app/components/chat/contact-info/contact-info.component.ts @@ -0,0 +1,28 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDrawer } from '@angular/material/sidenav'; +import { Chat } from 'app/components/chat/chat.types'; + +@Component({ + selector: 'chat-contact-info', + templateUrl: './contact-info.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [MatButtonModule, MatIconModule], +}) +export class ContactInfoComponent { + @Input() chat: Chat; + @Input() drawer: MatDrawer; + + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/chat/conversation/conversation.component.html b/src/app/components/chat/conversation/conversation.component.html new file mode 100644 index 0000000..16c5311 --- /dev/null +++ b/src/app/components/chat/conversation/conversation.component.html @@ -0,0 +1,300 @@ +
+ @if (chat) { + + + + + + + + + + +
+ + + + + + +
+
+ @if (chat.contact.avatar) { + Contact avatar + } + @if (!chat.contact.avatar) { +
+ {{ chat.contact.name.charAt(0) }} +
+ } +
+
+ {{ chat.contact.name }} +
+
+ + + + + + + + +
+ + +
+
+ @for ( + message of chat.messages; + track trackByFn(i, message); + let i = $index; + let first = $first; + let last = $last + ) { + + @if ( + first || + (chat.messages[i - 1].createdAt | date: 'd') !== + (message.createdAt | date: 'd') + ) { +
+
+
+ {{ + message.createdAt | date: 'longDate' + }} +
+
+
+ } +
+ +
+ + @if ( + last || + chat.messages[i + 1].isMine !== + message.isMine + ) { +
+ +
+ } + +
+
+ + @if ( + first || + last || + chat.messages[i + 1].isMine !== + message.isMine || + chat.messages[i + 1].createdAt !== + message.createdAt + ) { +
+ {{ message.createdAt | date: 'HH:mm' }} +
+ } +
+ } +
+
+ + +
+
+ + +
+ + + +
+ +
+
+
+
+ } @else { +
+ +
+ Select a conversation or start a new chat +
+
+ } + + + + + + + + + + + + +
diff --git a/src/app/components/chat/conversation/conversation.component.ts b/src/app/components/chat/conversation/conversation.component.ts new file mode 100644 index 0000000..1a82c10 --- /dev/null +++ b/src/app/components/chat/conversation/conversation.component.ts @@ -0,0 +1,187 @@ +import { TextFieldModule } from '@angular/cdk/text-field'; +import { DatePipe, NgClass, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + ElementRef, + HostListener, + NgZone, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { RouterLink } from '@angular/router'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { ChatService } from 'app/components/chat/chat.service'; +import { Chat } from 'app/components/chat/chat.types'; +import { ContactInfoComponent } from 'app/components/chat/contact-info/contact-info.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'chat-conversation', + templateUrl: './conversation.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatSidenavModule, + ContactInfoComponent, + MatButtonModule, + RouterLink, + MatIconModule, + MatMenuModule, + NgClass, + NgTemplateOutlet, + MatFormFieldModule, + MatInputModule, + TextFieldModule, + DatePipe, + ], +}) +export class ConversationComponent implements OnInit, OnDestroy { + @ViewChild('messageInput') messageInput: ElementRef; + chat: Chat; + drawerMode: 'over' | 'side' = 'side'; + drawerOpened: boolean = false; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _chatService: ChatService, + private _angorMediaWatcherService: AngorMediaWatcherService, + private _ngZone: NgZone + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Decorated methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Resize on 'input' and 'ngModelChange' events + * + * @private + */ + @HostListener('input') + @HostListener('ngModelChange') + private _resizeMessageInput(): void { + // This doesn't need to trigger Angular's change detection by itself + this._ngZone.runOutsideAngular(() => { + setTimeout(() => { + // Set the height to 'auto' so we can correctly read the scrollHeight + this.messageInput.nativeElement.style.height = 'auto'; + + // Detect the changes so the height is applied + this._changeDetectorRef.detectChanges(); + + // Get the scrollHeight and subtract the vertical padding + this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`; + + // Detect the changes one more time to apply the final height + this._changeDetectorRef.detectChanges(); + }); + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Chat + this._chatService.chat$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chat: Chat) => { + this.chat = chat; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + // Subscribe to media changes + this._angorMediaWatcherService.onMediaChange$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(({ matchingAliases }) => { + // Set the drawerMode if the given breakpoint is active + if (matchingAliases.includes('lg')) { + this.drawerMode = 'side'; + } else { + this.drawerMode = 'over'; + } + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Open the contact info + */ + openContactInfo(): void { + // Open the drawer + this.drawerOpened = true; + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Reset the chat + */ + resetChat(): void { + this._chatService.resetChat(); + + // Close the contact info in case it's opened + this.drawerOpened = false; + + // Mark for check + this._changeDetectorRef.markForCheck(); + } + + /** + * Toggle mute notifications + */ + toggleMuteNotifications(): void { + // Toggle the muted + this.chat.muted = !this.chat.muted; + + // Update the chat on the server + this._chatService.updateChat(this.chat.id, this.chat).subscribe(); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/chat/empty-conversation/empty-conversation.component.html b/src/app/components/chat/empty-conversation/empty-conversation.component.html new file mode 100644 index 0000000..2e1591d --- /dev/null +++ b/src/app/components/chat/empty-conversation/empty-conversation.component.html @@ -0,0 +1,16 @@ +
+ +
+ +
+ Select a conversation or start a new chat +
+
+
diff --git a/src/app/components/chat/empty-conversation/empty-conversation.component.ts b/src/app/components/chat/empty-conversation/empty-conversation.component.ts new file mode 100644 index 0000000..1e27c13 --- /dev/null +++ b/src/app/components/chat/empty-conversation/empty-conversation.component.ts @@ -0,0 +1,21 @@ +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; + +@Component({ + selector: 'chat-empty-conversation', + templateUrl: './empty-conversation.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [MatIconModule], +}) +export class EmptyConversationComponent { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/chat/new-chat/new-chat.component.html b/src/app/components/chat/new-chat/new-chat.component.html new file mode 100644 index 0000000..0cac936 --- /dev/null +++ b/src/app/components/chat/new-chat/new-chat.component.html @@ -0,0 +1,76 @@ +
+ +
+ +
New chat
+
+ +
+ @if (contacts.length) { + @for ( + contact of contacts; + track trackByFn(i, contact); + let i = $index + ) { + + @if ( + i === 0 || + contact.name.charAt(0) !== contacts[i - 1].name.charAt(0) + ) { +
+ {{ contact.name.charAt(0) }} +
+ } + +
+
+ @if (contact.avatar) { + Contact avatar + } + @if (!contact.avatar) { +
+ {{ contact.name.charAt(0) }} +
+ } +
+
+
+ {{ contact.name }} +
+
+ {{ contact.about }} +
+
+
+ } + } @else { +
+ There are no contacts! +
+ } +
+ + +
diff --git a/src/app/components/chat/new-chat/new-chat.component.ts b/src/app/components/chat/new-chat/new-chat.component.ts new file mode 100644 index 0000000..796aa74 --- /dev/null +++ b/src/app/components/chat/new-chat/new-chat.component.ts @@ -0,0 +1,72 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnDestroy, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDrawer } from '@angular/material/sidenav'; +import { ChatService } from 'app/components/chat/chat.service'; +import { Contact } from 'app/components/chat/chat.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'chat-new-chat', + templateUrl: './new-chat.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [MatButtonModule, MatIconModule], +}) +export class NewChatComponent implements OnInit, OnDestroy { + @Input() drawer: MatDrawer; + contacts: Contact[] = []; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor(private _chatService: ChatService) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Contacts + this._chatService.contacts$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((contacts: Contact[]) => { + this.contacts = contacts; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/chat/profile/profile.component.html b/src/app/components/chat/profile/profile.component.html new file mode 100644 index 0000000..6b5e331 --- /dev/null +++ b/src/app/components/chat/profile/profile.component.html @@ -0,0 +1,83 @@ +
+ +
+ +
Profile
+
+ +
+ +
+ + @if (profile.avatar) { + + } + @if (!profile.avatar) { +
+ {{ profile.name.charAt(0) }} +
+ } +
+ + +
+ + Name + + + + + Email + + + + + About + + + +
+ + +
+
+
+
diff --git a/src/app/components/chat/profile/profile.component.ts b/src/app/components/chat/profile/profile.component.ts new file mode 100644 index 0000000..fe44645 --- /dev/null +++ b/src/app/components/chat/profile/profile.component.ts @@ -0,0 +1,67 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnDestroy, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatDrawer } from '@angular/material/sidenav'; +import { ChatService } from 'app/components/chat/chat.service'; +import { Profile } from 'app/components/chat/chat.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'chat-profile', + templateUrl: './profile.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatButtonModule, + MatIconModule, + MatFormFieldModule, + MatInputModule, + FormsModule, + ], +}) +export class ProfileComponent implements OnInit, OnDestroy { + @Input() drawer: MatDrawer; + profile: Profile; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor(private _chatService: ChatService) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Profile + this._chatService.profile$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((profile: Profile) => { + this.profile = profile; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/app/components/explore/explore.component.html b/src/app/components/explore/explore.component.html new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/src/app/components/explore/explore.component.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/components/explore/explore.component.ts b/src/app/components/explore/explore.component.ts new file mode 100644 index 0000000..95b5c86 --- /dev/null +++ b/src/app/components/explore/explore.component.ts @@ -0,0 +1,17 @@ +import { Component, ViewEncapsulation } from '@angular/core'; + +@Component({ + selector : 'explore', + standalone : true, + templateUrl : './explore.component.html', + encapsulation: ViewEncapsulation.None, +}) +export class ExampleComponent +{ + /** + * Constructor + */ + constructor() + { + } +} diff --git a/src/app/components/explore/explore.routes.ts b/src/app/components/explore/explore.routes.ts new file mode 100644 index 0000000..e19dfd2 --- /dev/null +++ b/src/app/components/explore/explore.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { ExampleComponent } from 'app/components/explore/explore.component'; + +export default [ + { + path : '', + component: ExampleComponent, + }, +] as Routes; diff --git a/src/app/components/home/home.component.html b/src/app/components/home/home.component.html new file mode 100644 index 0000000..99b62ab --- /dev/null +++ b/src/app/components/home/home.component.html @@ -0,0 +1,28 @@ +
+
+
+ Angor Hub +

Welcome to Angor Hub

+

+ Angor Hub is a decentralized platform designed to connect investors with project founders. Leveraging the power of Nostr, it provides a secure and transparent environment for collaboration. The platform allows you to explore a wide variety of projects, engage with investors, and connect directly with founders. +

+

+ Whether you're an investor looking for the next big opportunity or a project founder seeking support, Angor Hub offers the tools you need to succeed. From secure messaging to group channels, Angor Hub ensures seamless interaction within a decentralized network. +

+
+ +
+
diff --git a/src/app/components/home/home.component.ts b/src/app/components/home/home.component.ts new file mode 100644 index 0000000..ff29176 --- /dev/null +++ b/src/app/components/home/home.component.ts @@ -0,0 +1,18 @@ +import { Component, ViewEncapsulation } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'landing-home', + templateUrl: './home.component.html', + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [MatButtonModule, RouterLink, MatIconModule], +}) +export class LandingHomeComponent { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/home/home.routes.ts b/src/app/components/home/home.routes.ts new file mode 100644 index 0000000..e5ce52a --- /dev/null +++ b/src/app/components/home/home.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { LandingHomeComponent } from 'app/components/home/home.component'; + +export default [ + { + path: '', + component: LandingHomeComponent, + }, +] as Routes; diff --git a/src/app/components/pages/error/error-404/error-404.component.html b/src/app/components/pages/error/error-404/error-404.component.html new file mode 100644 index 0000000..d90a26b --- /dev/null +++ b/src/app/components/pages/error/error-404/error-404.component.html @@ -0,0 +1,74 @@ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Ooops... 404! +
+
+ The page you requested could not be found. +
+ + Back to Dashboard + +
+
diff --git a/src/app/components/pages/error/error-404/error-404.component.ts b/src/app/components/pages/error/error-404/error-404.component.ts new file mode 100644 index 0000000..a785ebd --- /dev/null +++ b/src/app/components/pages/error/error-404/error-404.component.ts @@ -0,0 +1,21 @@ +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'error-404', + templateUrl: './error-404.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [RouterLink], +}) +export class Error404Component { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/pages/error/error-404/error-404.routes.ts b/src/app/components/pages/error/error-404/error-404.routes.ts new file mode 100644 index 0000000..31c34f4 --- /dev/null +++ b/src/app/components/pages/error/error-404/error-404.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { Error404Component } from 'app/components/pages/error/error-404/error-404.component'; + +export default [ + { + path: '', + component: Error404Component, + }, +] as Routes; diff --git a/src/app/components/pages/error/error-500/error-500.component.html b/src/app/components/pages/error/error-500/error-500.component.html new file mode 100644 index 0000000..bb08387 --- /dev/null +++ b/src/app/components/pages/error/error-500/error-500.component.html @@ -0,0 +1,84 @@ +
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ Something went wrong! +
+
+ Server Error 500. Our staff has been notified, thank you for your + understanding. +
+ + Back to Dashboard + +
+
diff --git a/src/app/components/pages/error/error-500/error-500.component.ts b/src/app/components/pages/error/error-500/error-500.component.ts new file mode 100644 index 0000000..9816df8 --- /dev/null +++ b/src/app/components/pages/error/error-500/error-500.component.ts @@ -0,0 +1,21 @@ +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'error-500', + templateUrl: './error-500.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [RouterLink], +}) +export class Error500Component { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/pages/error/error-500/error-500.routes.ts b/src/app/components/pages/error/error-500/error-500.routes.ts new file mode 100644 index 0000000..c8dcb3e --- /dev/null +++ b/src/app/components/pages/error/error-500/error-500.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { Error500Component } from 'app/components/admin/pages/error/error-500/error-500.component'; + +export default [ + { + path: '', + component: Error500Component, + }, +] as Routes; diff --git a/src/app/components/pages/error/settings/account/account.component.html b/src/app/components/pages/error/settings/account/account.component.html new file mode 100644 index 0000000..55cba56 --- /dev/null +++ b/src/app/components/pages/error/settings/account/account.component.html @@ -0,0 +1,168 @@ +
+ +
+ +
+
Profile
+
+ Following information is publicly displayed, be careful! +
+
+
+ +
+ + Name + + + +
+ +
+ + Username +
angortheme.com/
+ +
+
+ +
+ + Title + + + +
+ +
+ + Company + + + +
+ +
+ + About + + +
+ Brief description for your profile. Basic HTML and Emoji are + allowed. +
+
+
+ + +
+ + +
+
Personal Information
+
+ Communication details in case we want to connect with you. These + will be kept private. +
+
+
+ +
+ + Email + + + +
+ +
+ + Phone + + + +
+ +
+ + Country + + + United States + Canada + Mexico + France + Germany + Italy + + +
+ +
+ + Language + + + English + French + Spanish + German + Italian + + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/pages/error/settings/account/account.component.ts b/src/app/components/pages/error/settings/account/account.component.ts new file mode 100644 index 0000000..6ba8b01 --- /dev/null +++ b/src/app/components/pages/error/settings/account/account.component.ts @@ -0,0 +1,71 @@ +import { TextFieldModule } from '@angular/cdk/text-field'; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; + +@Component({ + selector: 'settings-account', + templateUrl: './account.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + TextFieldModule, + MatSelectModule, + MatOptionModule, + MatButtonModule, + ], +}) +export class SettingsAccountComponent implements OnInit { + accountForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.accountForm = this._formBuilder.group({ + name: ['Display Name'], + username: ['brianh'], + title: ['Senior Frontend Developer'], + company: ['YXZ Software'], + about: [ + "Hey! This is Brian; husband, father and gamer. I'm mostly passionate about bleeding edge tech and chocolate! 🍫", + ], + email: ['hughes.brian@mail.com', Validators.email], + phone: ['121-490-33-12'], + country: ['usa'], + language: ['english'], + }); + } +} diff --git a/src/app/components/pages/error/settings/notifications/notifications.component.html b/src/app/components/pages/error/settings/notifications/notifications.component.html new file mode 100644 index 0000000..190dc4c --- /dev/null +++ b/src/app/components/pages/error/settings/notifications/notifications.component.html @@ -0,0 +1,156 @@ +
+ +
+ +
Alerts
+
+ +
+
+
Communication
+
+ Get news, announcements, and product updates. +
+
+ + +
+ +
+
+
Security
+
+ Get important notifications about your account security. +
+
+ + +
+ +
+
+
Meetups
+
+ Get an email when a Meetup is posted close to my + location. +
+
+ + +
+
+ + +
+ + +
Account Activity
+
Email me when:
+
+ +
+
+ someone comments on one of my items +
+ + +
+ +
+
+ someone mentions me +
+ + +
+ +
+
+ someone follows me +
+ + +
+ +
+
+ someone replies to my job posting +
+ + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/pages/error/settings/notifications/notifications.component.ts b/src/app/components/pages/error/settings/notifications/notifications.component.ts new file mode 100644 index 0000000..7d75b0f --- /dev/null +++ b/src/app/components/pages/error/settings/notifications/notifications.component.ts @@ -0,0 +1,56 @@ +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +@Component({ + selector: 'settings-notifications', + templateUrl: './notifications.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatSlideToggleModule, + MatButtonModule, + ], +}) +export class SettingsNotificationsComponent implements OnInit { + notificationsForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.notificationsForm = this._formBuilder.group({ + communication: [true], + security: [true], + meetups: [false], + comments: [false], + mention: [true], + follow: [true], + inquiry: [true], + }); + } +} diff --git a/src/app/components/pages/error/settings/plan-billing/plan-billing.component.html b/src/app/components/pages/error/settings/plan-billing/plan-billing.component.html new file mode 100644 index 0000000..efe7a3c --- /dev/null +++ b/src/app/components/pages/error/settings/plan-billing/plan-billing.component.html @@ -0,0 +1,171 @@ +
+ +
+ +
+
Change your plan
+
+ Upgrade or downgrade your current plan. +
+
+
+ +
+ + Changing the plan will take effect immediately. You will be + charged for the rest of the current month. + +
+ + @for (plan of plans; track trackByFn($index, plan)) { +
+ @if (planRadioGroup.value === plan.value) { + + } +
{{ plan.label }}
+
+ {{ plan.details }} +
+
+
+ {{ + plan.price | currency: 'USD' : 'symbol' : '1.0' + }} + / month +
+
+ } +
+ + +
+ + +
+
Payment Details
+
+ Update your billing information. Make sure to set your location + correctly as it could affect your tax rates. +
+
+
+ +
+ + Card holder + + + +
+ +
+ + Card number + + + +
+ +
+ + Expiration date + + + +
+ +
+ + CVC / CVC2 + + + +
+ +
+ + Country + + + United States + Canada + Mexico + France + Germany + Italy + + +
+ +
+ + ZIP / Postal code + + + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/pages/error/settings/plan-billing/plan-billing.component.ts b/src/app/components/pages/error/settings/plan-billing/plan-billing.component.ts new file mode 100644 index 0000000..1495891 --- /dev/null +++ b/src/app/components/pages/error/settings/plan-billing/plan-billing.component.ts @@ -0,0 +1,108 @@ +import { CurrencyPipe, NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatRadioModule } from '@angular/material/radio'; +import { MatSelectModule } from '@angular/material/select'; +import { AngorAlertComponent } from '@angor/components/alert'; + +@Component({ + selector: 'settings-plan-billing', + templateUrl: './plan-billing.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + AngorAlertComponent, + MatRadioModule, + NgClass, + MatIconModule, + MatFormFieldModule, + MatInputModule, + MatSelectModule, + MatOptionModule, + MatButtonModule, + CurrencyPipe, + ], +}) +export class SettingsPlanBillingComponent implements OnInit { + planBillingForm: UntypedFormGroup; + plans: any[]; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.planBillingForm = this._formBuilder.group({ + plan: ['team'], + cardHolder: ['Display Name'], + cardNumber: [''], + cardExpiration: [''], + cardCVC: [''], + country: ['usa'], + zip: [''], + }); + + // Setup the plans + this.plans = [ + { + value: 'basic', + label: 'BASIC', + details: 'Starter plan for individuals.', + price: '10', + }, + { + value: 'team', + label: 'TEAM', + details: 'Collaborate up to 10 people.', + price: '20', + }, + { + value: 'enterprise', + label: 'ENTERPRISE', + details: 'For bigger businesses.', + price: '40', + }, + ]; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/pages/error/settings/security/security.component.html b/src/app/components/pages/error/settings/security/security.component.html new file mode 100644 index 0000000..7cca822 --- /dev/null +++ b/src/app/components/pages/error/settings/security/security.component.html @@ -0,0 +1,123 @@ +
+ +
+ +
+
Change your password
+
+ You can only change your password twice within 24 hours! +
+
+
+ +
+ + Current password + + + +
+ +
+ + New password + + + +
+ Minimum 8 characters. Must include numbers, letters and + special characters. +
+
+
+ + +
+ + +
+
Security preferences
+
+ Keep your account more secure with following preferences. +
+
+
+ +
+
+
+ Enable 2-step authentication +
+
+ Protects you against password theft by requesting an + authentication code via SMS on every login. +
+
+ + +
+ +
+
+
+ Ask to change password on every 6 months +
+
+ A simple but an effective way to be protected against + data leaks and password theft. +
+
+ + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/pages/error/settings/security/security.component.ts b/src/app/components/pages/error/settings/security/security.component.ts new file mode 100644 index 0000000..175f67b --- /dev/null +++ b/src/app/components/pages/error/settings/security/security.component.ts @@ -0,0 +1,59 @@ +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +@Component({ + selector: 'settings-security', + templateUrl: './security.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatSlideToggleModule, + MatButtonModule, + ], +}) +export class SettingsSecurityComponent implements OnInit { + securityForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.securityForm = this._formBuilder.group({ + currentPassword: [''], + newPassword: [''], + twoStep: [true], + askPasswordChange: [false], + }); + } +} diff --git a/src/app/components/pages/error/settings/settings.component.html b/src/app/components/pages/error/settings/settings.component.html new file mode 100644 index 0000000..963e198 --- /dev/null +++ b/src/app/components/pages/error/settings/settings.component.html @@ -0,0 +1,126 @@ +
+ + + + +
+ +
+ Settings +
+ +
+ +
+
+ +
+ @for (panel of panels; track trackByFn($index, panel)) { +
+ +
+
+ {{ panel.title }} +
+
+ {{ panel.description }} +
+
+
+ } +
+
+ + + + +
+ +
+ + + + +
+ {{ getPanelInfo(selectedPanel).title }} +
+
+ + +
+ @switch (selectedPanel) { + + @case ('account') { + + } + + @case ('security') { + + } + + @case ('plan-billing') { + + } + + @case ('notifications') { + + } + + @case ('team') { + + } + } +
+
+
+
+
diff --git a/src/app/components/pages/error/settings/settings.component.ts b/src/app/components/pages/error/settings/settings.component.ts new file mode 100644 index 0000000..6bce8d9 --- /dev/null +++ b/src/app/components/pages/error/settings/settings.component.ts @@ -0,0 +1,165 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { Subject, takeUntil } from 'rxjs'; +import { SettingsAccountComponent } from './account/account.component'; +import { SettingsNotificationsComponent } from './notifications/notifications.component'; +import { SettingsPlanBillingComponent } from './plan-billing/plan-billing.component'; +import { SettingsSecurityComponent } from './security/security.component'; +import { SettingsTeamComponent } from './team/team.component'; + +@Component({ + selector: 'settings', + templateUrl: './settings.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatSidenavModule, + MatButtonModule, + MatIconModule, + NgClass, + SettingsAccountComponent, + SettingsSecurityComponent, + SettingsPlanBillingComponent, + SettingsNotificationsComponent, + SettingsTeamComponent, + ], +}) +export class SettingsComponent implements OnInit, OnDestroy { + @ViewChild('drawer') drawer: MatDrawer; + drawerMode: 'over' | 'side' = 'side'; + drawerOpened: boolean = true; + panels: any[] = []; + selectedPanel: string = 'account'; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _angorMediaWatcherService: AngorMediaWatcherService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Setup available panels + this.panels = [ + { + id: 'account', + icon: 'heroicons_outline:user-circle', + title: 'Account', + description: + 'Manage your public profile and private information', + }, + { + id: 'security', + icon: 'heroicons_outline:lock-closed', + title: 'Security', + description: + 'Manage your password and 2-step verification preferences', + }, + { + id: 'plan-billing', + icon: 'heroicons_outline:credit-card', + title: 'Plan & Billing', + description: + 'Manage your subscription plan, payment method and billing information', + }, + { + id: 'notifications', + icon: 'heroicons_outline:bell', + title: 'Notifications', + description: "Manage when you'll be notified on which channels", + }, + { + id: 'team', + icon: 'heroicons_outline:user-group', + title: 'Team', + description: + 'Manage your existing team and change roles/permissions', + }, + ]; + + // Subscribe to media changes + this._angorMediaWatcherService.onMediaChange$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(({ matchingAliases }) => { + // Set the drawerMode and drawerOpened + if (matchingAliases.includes('lg')) { + this.drawerMode = 'side'; + this.drawerOpened = true; + } else { + this.drawerMode = 'over'; + this.drawerOpened = false; + } + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Navigate to the panel + * + * @param panel + */ + goToPanel(panel: string): void { + this.selectedPanel = panel; + + // Close the drawer on 'over' mode + if (this.drawerMode === 'over') { + this.drawer.close(); + } + } + + /** + * Get the details of the panel + * + * @param id + */ + getPanelInfo(id: string): any { + return this.panels.find((panel) => panel.id === id); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/pages/error/settings/settings.routes.ts b/src/app/components/pages/error/settings/settings.routes.ts new file mode 100644 index 0000000..0704a7c --- /dev/null +++ b/src/app/components/pages/error/settings/settings.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { SettingsComponent } from 'app/components/settings/settings.component'; + +export default [ + { + path: '', + component: SettingsComponent, + }, +] as Routes; diff --git a/src/app/components/pages/error/settings/team/team.component.html b/src/app/components/pages/error/settings/team/team.component.html new file mode 100644 index 0000000..da86c5a --- /dev/null +++ b/src/app/components/pages/error/settings/team/team.component.html @@ -0,0 +1,99 @@ +
+ +
+ + Add team members + + + + +
+ + +
+ @for (member of members; track trackByFn($index, member)) { +
+
+
+ @if (member.avatar) { + Contact avatar + } + @if (!member.avatar) { +
+ {{ member.name.charAt(0) }} +
+ } +
+
+
{{ member.name }}
+
{{ member.email }}
+
+
+
+
+ + + + Role: + {{ + roleSelect.value | titlecase + }} + + @for (role of roles; track role) { + +
+ {{ role.label }} +
+
+ {{ role.description }} +
+
+ } +
+
+
+
+ +
+
+
+ } +
+
diff --git a/src/app/components/pages/error/settings/team/team.component.ts b/src/app/components/pages/error/settings/team/team.component.ts new file mode 100644 index 0000000..403a832 --- /dev/null +++ b/src/app/components/pages/error/settings/team/team.component.ts @@ -0,0 +1,130 @@ +import { TitleCasePipe } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; + +@Component({ + selector: 'settings-team', + templateUrl: './team.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + MatOptionModule, + TitleCasePipe, + ], +}) +export class SettingsTeamComponent implements OnInit { + members: any[]; + roles: any[]; + + /** + * Constructor + */ + constructor() {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Setup the team members + this.members = [ + { + avatar: 'images/avatars/male-01.jpg', + name: 'Dejesus Michael', + email: 'dejesusmichael@mail.org', + role: 'admin', + }, + { + avatar: 'images/avatars/male-03.jpg', + name: 'Mclaughlin Steele', + email: 'mclaughlinsteele@mail.me', + role: 'admin', + }, + { + avatar: 'images/avatars/female-02.jpg', + name: 'Laverne Dodson', + email: 'lavernedodson@mail.ca', + role: 'write', + }, + { + avatar: 'images/avatars/female-03.jpg', + name: 'Trudy Berg', + email: 'trudyberg@mail.us', + role: 'read', + }, + { + avatar: 'images/avatars/male-07.jpg', + name: 'Lamb Underwood', + email: 'lambunderwood@mail.me', + role: 'read', + }, + { + avatar: 'images/avatars/male-08.jpg', + name: 'Mcleod Wagner', + email: 'mcleodwagner@mail.biz', + role: 'read', + }, + { + avatar: 'images/avatars/female-07.jpg', + name: 'Shannon Kennedy', + email: 'shannonkennedy@mail.ca', + role: 'read', + }, + ]; + + // Setup the roles + this.roles = [ + { + label: 'Read', + value: 'read', + description: + 'Can read and clone this repository. Can also open and comment on issues and pull requests.', + }, + { + label: 'Write', + value: 'write', + description: + 'Can read, clone, and push to this repository. Can also manage issues and pull requests.', + }, + { + label: 'Admin', + value: 'admin', + description: + 'Can read, clone, and push to this repository. Can also manage issues, pull requests, and repository settings, including adding collaborators.', + }, + ]; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/profile/profile.component.html b/src/app/components/profile/profile.component.html new file mode 100644 index 0000000..620afe7 --- /dev/null +++ b/src/app/components/profile/profile.component.html @@ -0,0 +1,2155 @@ +
+ +
+ +
+ Cover image +
+ + +
+ +
+ User avatar +
+ + +
+
Display Name
+
London, UK
+
+ + + + + +
+
+ 200k + FOLLOWERS +
+
+ 1.2k + FOLLOWING +
+
+ + + +
+
+ + +
+ + + + +
+ + +
Create Post
+
+
+ Card cover image +
Display Name
+
+ + + +
+
+ + + + + + + + + + + +
+
+ + + +
+ Card cover image +
+ Caroline Lundu + 29 minutes ago +
+ + + + + + + + + + +
+
+ Look at that sky! I so want to be there.. Can we arrange a + trip? Is that a possibility? Please!!! +
+
+ Card cover image +
+ +
+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+ + +
+
+
+ Card cover image + + + +
+
+ + + +
+
+
+
+
+
+ Card cover image +
+ + Rutherford Brannan Oh, I’m in.. + Let’s arrange a trip for the next + weekend if you want! + +
+ Like + Reply + Hide replies + + 17 min +
+
+
+
+ Card cover image +
+ + Caroline Lundu Yes!! Let's talk + about it on lunch! + +
+ Like + Reply + + 15 min +
+
+
+
+ Card cover image +
+ + Barbara Cotilla Count me in !!! + +
+ Like + Reply + + 12 min +
+
+
+
+ Card cover image +
+ + Alan Marti The color of the sky + doesn’t look natural at all, do you + really think this is natural? I’d say + Photoshop! Your trip isn't going to + worth it since you won't be seeing this + exact sky. + +
+ Like + Reply + Hide replies + + 24 min +
+
+
+
+ Card cover image +
+ + Caroline Lundu Hey, Alan! You + must be fun at parties! + +
+ Like + Reply + + 22 min +
+
+
+
+ Card cover image +
+ + Alan Marti Caroline, I'm telling + the truth, and if you cannot stand the + truth, maybe we shouldn't be friends + anymore... + +
+ Like + Reply + + 20 min +
+
+
+
+ Card cover image +
+ + Caroline Lundu Dude! Relax! I'm + just messing with you... + +
+ Like + Reply + + 18 min +
+
+
+
+ Card cover image +
+ + Alan Marti Sorry! I had a bad + morning, let's talk about this in couple + hours, I need to relax a bit :( + +
+ Like + Reply + + 16 min +
+
+
+
+ Card cover image +
+ + Marleah Eagleston Count me in, + too! + +
+ Like + Reply + + 34 min +
+
+
+
+
+
+
+ + + +
+ Card cover image +
+ Caroline Lundu + 29 minutes ago +
+ + + + + + + + + + +
+
+

+ We'll put a happy little sky in here. We touch the + canvas, the canvas takes what it wants. A little happy + sunlight shining through there. Let's build some happy + little clouds up here. I was blessed with a very steady + hand; and it comes in very handy when you're doing these + little delicate things. This is the fun part. +

+

+ Isn't it great to do something you can't fail at? Little + trees and bushes grow however makes them happy. Trees + get lonely too, so we'll give him a little friend. There + are no mistakes. You can fix anything that happens. +

+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+
+ + + +
+ Card cover image +
+ Marleah Eagleston + 29 minutes ago +
+ + + + + + + + + + +
+
+ Look at that sky! I so want to be there.. Can we arrange a + trip? Is that a possibility? Please!!! +
+
+
+ Card cover image +
+
+
+ Card cover image +
+
+ Card cover image +
+
+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+
+ + + +
+ Card cover image +
+ Caroline Lundu + 29 minutes ago +
+ + + + + + + + + + +
+
+ Hey!! I never saw this one, it was amazing.. I think I’m + going to buy myself a set and try his technique at home! +
+
+
+
+ Card cover image +
+
+
+ Take a look behind the scenes of Rob Boss + episodes +
+
+ We'll put a happy little sky in here. We touch + the canvas, the canvas takes what it wants. A + little happy sunlight shining through there. +
+
+ example.com +
+
+
+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+
+ + + +
+ Card cover image +
+ Marleah Eagleston + 29 minutes ago +
+ + + + + + + + + + +
+
+ Look at that sky! I so want to be there.. Can we arrange a + trip? Is that a possibility? Please!!! +
+
+
+ Card cover image +
+
+ Card cover image +
+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+
+ + + +
+ Card cover image +
+ Caroline Lundu + 29 minutes ago +
+ + + + + + + + + + +
+
+ Hey!! I never saw this episode, it was amazing.. I think I’m + going to buy myself a set and try his technique at home! +
+
+
+
+ Card cover image +
+
+
+ Rob Boss - Season 09 Episode 04 +
+
+ We'll put a happy little sky in here. We touch + the canvas, the canvas takes what it wants. A + little happy sunlight shining through there. +
+
+ example.com +
+
+
+
+
+ + + +
+
+
+
+ Card cover image + Card cover image + Card cover image + Card cover image +
+ You and 24 more liked this +
+
+ +
+ + +
+
+
+
+
+
diff --git a/src/app/components/profile/profile.component.ts b/src/app/components/profile/profile.component.ts new file mode 100644 index 0000000..d7c6341 --- /dev/null +++ b/src/app/components/profile/profile.component.ts @@ -0,0 +1,43 @@ +import { TextFieldModule } from '@angular/cdk/text-field'; +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterLink } from '@angular/router'; +import { AngorCardComponent } from '@angor/components/card'; + +@Component({ + selector: 'profile', + templateUrl: './profile.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + RouterLink, + AngorCardComponent, + MatIconModule, + MatButtonModule, + MatMenuModule, + MatFormFieldModule, + MatInputModule, + TextFieldModule, + MatDividerModule, + MatTooltipModule, + NgClass, + ], +}) +export class ProfileComponent { + /** + * Constructor + */ + constructor() {} +} diff --git a/src/app/components/profile/profile.routes.ts b/src/app/components/profile/profile.routes.ts new file mode 100644 index 0000000..e8e8e07 --- /dev/null +++ b/src/app/components/profile/profile.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { ProfileComponent } from 'app/components/profile/profile.component'; + +export default [ + { + path: '', + component: ProfileComponent, + }, +] as Routes; diff --git a/src/app/components/settings/account/account.component.html b/src/app/components/settings/account/account.component.html new file mode 100644 index 0000000..55cba56 --- /dev/null +++ b/src/app/components/settings/account/account.component.html @@ -0,0 +1,168 @@ +
+ +
+ +
+
Profile
+
+ Following information is publicly displayed, be careful! +
+
+
+ +
+ + Name + + + +
+ +
+ + Username +
angortheme.com/
+ +
+
+ +
+ + Title + + + +
+ +
+ + Company + + + +
+ +
+ + About + + +
+ Brief description for your profile. Basic HTML and Emoji are + allowed. +
+
+
+ + +
+ + +
+
Personal Information
+
+ Communication details in case we want to connect with you. These + will be kept private. +
+
+
+ +
+ + Email + + + +
+ +
+ + Phone + + + +
+ +
+ + Country + + + United States + Canada + Mexico + France + Germany + Italy + + +
+ +
+ + Language + + + English + French + Spanish + German + Italian + + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/settings/account/account.component.ts b/src/app/components/settings/account/account.component.ts new file mode 100644 index 0000000..6ba8b01 --- /dev/null +++ b/src/app/components/settings/account/account.component.ts @@ -0,0 +1,71 @@ +import { TextFieldModule } from '@angular/cdk/text-field'; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, + Validators, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; + +@Component({ + selector: 'settings-account', + templateUrl: './account.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + TextFieldModule, + MatSelectModule, + MatOptionModule, + MatButtonModule, + ], +}) +export class SettingsAccountComponent implements OnInit { + accountForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.accountForm = this._formBuilder.group({ + name: ['Display Name'], + username: ['brianh'], + title: ['Senior Frontend Developer'], + company: ['YXZ Software'], + about: [ + "Hey! This is Brian; husband, father and gamer. I'm mostly passionate about bleeding edge tech and chocolate! 🍫", + ], + email: ['hughes.brian@mail.com', Validators.email], + phone: ['121-490-33-12'], + country: ['usa'], + language: ['english'], + }); + } +} diff --git a/src/app/components/settings/notifications/notifications.component.html b/src/app/components/settings/notifications/notifications.component.html new file mode 100644 index 0000000..190dc4c --- /dev/null +++ b/src/app/components/settings/notifications/notifications.component.html @@ -0,0 +1,156 @@ +
+ +
+ +
Alerts
+
+ +
+
+
Communication
+
+ Get news, announcements, and product updates. +
+
+ + +
+ +
+
+
Security
+
+ Get important notifications about your account security. +
+
+ + +
+ +
+
+
Meetups
+
+ Get an email when a Meetup is posted close to my + location. +
+
+ + +
+
+ + +
+ + +
Account Activity
+
Email me when:
+
+ +
+
+ someone comments on one of my items +
+ + +
+ +
+
+ someone mentions me +
+ + +
+ +
+
+ someone follows me +
+ + +
+ +
+
+ someone replies to my job posting +
+ + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/settings/notifications/notifications.component.ts b/src/app/components/settings/notifications/notifications.component.ts new file mode 100644 index 0000000..7d75b0f --- /dev/null +++ b/src/app/components/settings/notifications/notifications.component.ts @@ -0,0 +1,56 @@ +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +@Component({ + selector: 'settings-notifications', + templateUrl: './notifications.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatSlideToggleModule, + MatButtonModule, + ], +}) +export class SettingsNotificationsComponent implements OnInit { + notificationsForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.notificationsForm = this._formBuilder.group({ + communication: [true], + security: [true], + meetups: [false], + comments: [false], + mention: [true], + follow: [true], + inquiry: [true], + }); + } +} diff --git a/src/app/components/settings/relay/relay.component.html b/src/app/components/settings/relay/relay.component.html new file mode 100644 index 0000000..da86c5a --- /dev/null +++ b/src/app/components/settings/relay/relay.component.html @@ -0,0 +1,99 @@ +
+ +
+ + Add team members + + + + +
+ + +
+ @for (member of members; track trackByFn($index, member)) { +
+
+
+ @if (member.avatar) { + Contact avatar + } + @if (!member.avatar) { +
+ {{ member.name.charAt(0) }} +
+ } +
+
+
{{ member.name }}
+
{{ member.email }}
+
+
+
+
+ + + + Role: + {{ + roleSelect.value | titlecase + }} + + @for (role of roles; track role) { + +
+ {{ role.label }} +
+
+ {{ role.description }} +
+
+ } +
+
+
+
+ +
+
+
+ } +
+
diff --git a/src/app/components/settings/relay/relay.component.ts b/src/app/components/settings/relay/relay.component.ts new file mode 100644 index 0000000..51f996a --- /dev/null +++ b/src/app/components/settings/relay/relay.component.ts @@ -0,0 +1,130 @@ +import { TitleCasePipe } from '@angular/common'; +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSelectModule } from '@angular/material/select'; + +@Component({ + selector: 'settings-relay', + templateUrl: './relay.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatButtonModule, + MatSelectModule, + MatOptionModule, + TitleCasePipe, + ], +}) +export class SettingsRoleComponent implements OnInit { + members: any[]; + roles: any[]; + + /** + * Constructor + */ + constructor() {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Setup the team members + this.members = [ + { + avatar: 'images/avatars/male-01.jpg', + name: 'Dejesus Michael', + email: 'dejesusmichael@mail.org', + role: 'admin', + }, + { + avatar: 'images/avatars/male-03.jpg', + name: 'Mclaughlin Steele', + email: 'mclaughlinsteele@mail.me', + role: 'admin', + }, + { + avatar: 'images/avatars/female-02.jpg', + name: 'Laverne Dodson', + email: 'lavernedodson@mail.ca', + role: 'write', + }, + { + avatar: 'images/avatars/female-03.jpg', + name: 'Trudy Berg', + email: 'trudyberg@mail.us', + role: 'read', + }, + { + avatar: 'images/avatars/male-07.jpg', + name: 'Lamb Underwood', + email: 'lambunderwood@mail.me', + role: 'read', + }, + { + avatar: 'images/avatars/male-08.jpg', + name: 'Mcleod Wagner', + email: 'mcleodwagner@mail.biz', + role: 'read', + }, + { + avatar: 'images/avatars/female-07.jpg', + name: 'Shannon Kennedy', + email: 'shannonkennedy@mail.ca', + role: 'read', + }, + ]; + + // Setup the roles + this.roles = [ + { + label: 'Read', + value: 'read', + description: + 'Can read and clone this repository. Can also open and comment on issues and pull requests.', + }, + { + label: 'Write', + value: 'write', + description: + 'Can read, clone, and push to this repository. Can also manage issues and pull requests.', + }, + { + label: 'Admin', + value: 'admin', + description: + 'Can read, clone, and push to this repository. Can also manage issues, pull requests, and repository settings, including adding collaborators.', + }, + ]; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/settings/security/security.component.html b/src/app/components/settings/security/security.component.html new file mode 100644 index 0000000..7cca822 --- /dev/null +++ b/src/app/components/settings/security/security.component.html @@ -0,0 +1,123 @@ +
+ +
+ +
+
Change your password
+
+ You can only change your password twice within 24 hours! +
+
+
+ +
+ + Current password + + + +
+ +
+ + New password + + + +
+ Minimum 8 characters. Must include numbers, letters and + special characters. +
+
+
+ + +
+ + +
+
Security preferences
+
+ Keep your account more secure with following preferences. +
+
+
+ +
+
+
+ Enable 2-step authentication +
+
+ Protects you against password theft by requesting an + authentication code via SMS on every login. +
+
+ + +
+ +
+
+
+ Ask to change password on every 6 months +
+
+ A simple but an effective way to be protected against + data leaks and password theft. +
+
+ + +
+
+ + +
+ + +
+ + +
+
+
diff --git a/src/app/components/settings/security/security.component.ts b/src/app/components/settings/security/security.component.ts new file mode 100644 index 0000000..175f67b --- /dev/null +++ b/src/app/components/settings/security/security.component.ts @@ -0,0 +1,59 @@ +import { + ChangeDetectionStrategy, + Component, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormBuilder, + UntypedFormGroup, +} from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { MatSlideToggleModule } from '@angular/material/slide-toggle'; + +@Component({ + selector: 'settings-security', + templateUrl: './security.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + FormsModule, + ReactiveFormsModule, + MatFormFieldModule, + MatIconModule, + MatInputModule, + MatSlideToggleModule, + MatButtonModule, + ], +}) +export class SettingsSecurityComponent implements OnInit { + securityForm: UntypedFormGroup; + + /** + * Constructor + */ + constructor(private _formBuilder: UntypedFormBuilder) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Create the form + this.securityForm = this._formBuilder.group({ + currentPassword: [''], + newPassword: [''], + twoStep: [true], + askPasswordChange: [false], + }); + } +} diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html new file mode 100644 index 0000000..042c3f2 --- /dev/null +++ b/src/app/components/settings/settings.component.html @@ -0,0 +1,122 @@ +
+ + + + +
+ +
+ Settings +
+ +
+ +
+
+ +
+ @for (panel of panels; track trackByFn($index, panel)) { +
+ +
+
+ {{ panel.title }} +
+
+ {{ panel.description }} +
+
+
+ } +
+
+ + + + +
+ +
+ + + + +
+ {{ getPanelInfo(selectedPanel).title }} +
+
+ + +
+ @switch (selectedPanel) { + + @case ('account') { + + } + + @case ('security') { + + } + + @case ('notifications') { + + } + + @case ('relay') { + + } + } +
+
+
+
+
diff --git a/src/app/components/settings/settings.component.ts b/src/app/components/settings/settings.component.ts new file mode 100644 index 0000000..cc7dcc3 --- /dev/null +++ b/src/app/components/settings/settings.component.ts @@ -0,0 +1,156 @@ +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatDrawer, MatSidenavModule } from '@angular/material/sidenav'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { Subject, takeUntil } from 'rxjs'; +import { SettingsAccountComponent } from './account/account.component'; +import { SettingsNotificationsComponent } from './notifications/notifications.component'; +import { SettingsSecurityComponent } from './security/security.component'; +import { SettingsRoleComponent } from './relay/relay.component'; + +@Component({ + selector: 'settings', + templateUrl: './settings.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: true, + imports: [ + MatSidenavModule, + MatButtonModule, + MatIconModule, + NgClass, + SettingsAccountComponent, + SettingsSecurityComponent, + SettingsNotificationsComponent, + SettingsRoleComponent, + ], +}) +export class SettingsComponent implements OnInit, OnDestroy { + @ViewChild('drawer') drawer: MatDrawer; + drawerMode: 'over' | 'side' = 'side'; + drawerOpened: boolean = true; + panels: any[] = []; + selectedPanel: string = 'account'; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _angorMediaWatcherService: AngorMediaWatcherService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Setup available panels + this.panels = [ + { + id: 'account', + icon: 'heroicons_outline:user-circle', + title: 'Account', + description: + 'Manage your public profile and private information', + }, + { + id: 'security', + icon: 'heroicons_outline:lock-closed', + title: 'Security', + description: + 'Manage your password and 2-step verification preferences', + }, + { + id: 'notifications', + icon: 'heroicons_outline:bell', + title: 'Notifications', + description: "Manage when you'll be notified on which channels", + }, + { + id: 'relay', + icon: 'heroicons_outline:computer-desktop', + title: 'Relay', + description: + 'Manage your existing relays and change roles/permissions', + }, + ]; + + // Subscribe to media changes + this._angorMediaWatcherService.onMediaChange$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(({ matchingAliases }) => { + // Set the drawerMode and drawerOpened + if (matchingAliases.includes('lg')) { + this.drawerMode = 'side'; + this.drawerOpened = true; + } else { + this.drawerMode = 'over'; + this.drawerOpened = false; + } + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Navigate to the panel + * + * @param panel + */ + goToPanel(panel: string): void { + this.selectedPanel = panel; + + // Close the drawer on 'over' mode + if (this.drawerMode === 'over') { + this.drawer.close(); + } + } + + /** + * Get the details of the panel + * + * @param id + */ + getPanelInfo(id: string): any { + return this.panels.find((panel) => panel.id === id); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/components/settings/settings.routes.ts b/src/app/components/settings/settings.routes.ts new file mode 100644 index 0000000..0704a7c --- /dev/null +++ b/src/app/components/settings/settings.routes.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { SettingsComponent } from 'app/components/settings/settings.component'; + +export default [ + { + path: '', + component: SettingsComponent, + }, +] as Routes; diff --git a/src/app/core/auth/auth.interceptor.ts b/src/app/core/auth/auth.interceptor.ts new file mode 100644 index 0000000..edfd77a --- /dev/null +++ b/src/app/core/auth/auth.interceptor.ts @@ -0,0 +1,62 @@ +import { + HttpErrorResponse, + HttpEvent, + HttpHandlerFn, + HttpRequest, +} from '@angular/common/http'; +import { inject } from '@angular/core'; +import { AuthService } from 'app/core/auth/auth.service'; +import { AuthUtils } from 'app/core/auth/auth.utils'; +import { Observable, catchError, throwError } from 'rxjs'; + +/** + * Intercept + * + * @param req + * @param next + */ +export const authInterceptor = ( + req: HttpRequest, + next: HttpHandlerFn +): Observable> => { + const authService = inject(AuthService); + + // Clone the request object + let newReq = req.clone(); + + // Request + // + // If the access token didn't expire, add the Authorization header. + // We won't add the Authorization header if the access token expired. + // This will force the server to return a "401 Unauthorized" response + // for the protected API routes which our response interceptor will + // catch and delete the access token from the local storage while logging + // the user out from the app. + if ( + authService.accessToken && + !AuthUtils.isTokenExpired(authService.accessToken) + ) { + newReq = req.clone({ + headers: req.headers.set( + 'Authorization', + 'Bearer ' + authService.accessToken + ), + }); + } + + // Response + return next(newReq).pipe( + catchError((error) => { + // Catch "401 Unauthorized" responses + if (error instanceof HttpErrorResponse && error.status === 401) { + // Sign out + authService.signOut(); + + // Reload the app + location.reload(); + } + + return throwError(error); + }) + ); +}; diff --git a/src/app/core/auth/auth.provider.ts b/src/app/core/auth/auth.provider.ts new file mode 100644 index 0000000..81beefe --- /dev/null +++ b/src/app/core/auth/auth.provider.ts @@ -0,0 +1,20 @@ +import { provideHttpClient, withInterceptors } from '@angular/common/http'; +import { + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + Provider, + inject, +} from '@angular/core'; +import { authInterceptor } from 'app/core/auth/auth.interceptor'; +import { AuthService } from 'app/core/auth/auth.service'; + +export const provideAuth = (): Array => { + return [ + provideHttpClient(withInterceptors([authInterceptor])), + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(AuthService), + multi: true, + }, + ]; +}; diff --git a/src/app/core/auth/auth.service.ts b/src/app/core/auth/auth.service.ts new file mode 100644 index 0000000..08c86b2 --- /dev/null +++ b/src/app/core/auth/auth.service.ts @@ -0,0 +1,178 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { AuthUtils } from 'app/core/auth/auth.utils'; +import { UserService } from 'app/core/user/user.service'; +import { catchError, Observable, of, switchMap, throwError } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class AuthService { + private _authenticated: boolean = false; + private _httpClient = inject(HttpClient); + private _userService = inject(UserService); + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Setter & getter for access token + */ + set accessToken(token: string) { + localStorage.setItem('accessToken', token); + } + + get accessToken(): string { + return localStorage.getItem('accessToken') ?? ''; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Forgot password + * + * @param email + */ + forgotPassword(email: string): Observable { + return this._httpClient.post('api/auth/forgot-password', email); + } + + /** + * Reset password + * + * @param password + */ + resetPassword(password: string): Observable { + return this._httpClient.post('api/auth/reset-password', password); + } + + /** + * Sign in + * + * @param credentials + */ + signIn(credentials: { email: string; password: string }): Observable { + // Throw error, if the user is already logged in + if (this._authenticated) { + return throwError('User is already logged in.'); + } + + return this._httpClient.post('api/auth/sign-in', credentials).pipe( + switchMap((response: any) => { + // Store the access token in the local storage + this.accessToken = response.accessToken; + + // Set the authenticated flag to true + this._authenticated = true; + + // Store the user on the user service + this._userService.user = response.user; + + // Return a new observable with the response + return of(response); + }) + ); + } + + /** + * Sign in using the access token + */ + signInUsingToken(): Observable { + // Sign in using the token + return this._httpClient + .post('api/auth/sign-in-with-token', { + accessToken: this.accessToken, + }) + .pipe( + catchError(() => + // Return false + of(false) + ), + switchMap((response: any) => { + // Replace the access token with the new one if it's available on + // the response object. + // + // This is an added optional step for better security. Once you sign + // in using the token, you should generate a new one on the server + // side and attach it to the response object. Then the following + // piece of code can replace the token with the refreshed one. + if (response.accessToken) { + this.accessToken = response.accessToken; + } + + // Set the authenticated flag to true + this._authenticated = true; + + // Store the user on the user service + this._userService.user = response.user; + + // Return true + return of(true); + }) + ); + } + + /** + * Sign out + */ + signOut(): Observable { + // Remove the access token from the local storage + localStorage.removeItem('accessToken'); + + // Set the authenticated flag to false + this._authenticated = false; + + // Return the observable + return of(true); + } + + /** + * Sign up + * + * @param user + */ + signUp(user: { + name: string; + email: string; + password: string; + company: string; + }): Observable { + return this._httpClient.post('api/auth/sign-up', user); + } + + /** + * Unlock session + * + * @param credentials + */ + unlockSession(credentials: { + email: string; + password: string; + }): Observable { + return this._httpClient.post('api/auth/unlock-session', credentials); + } + + /** + * Check the authentication status + */ + check(): Observable { + // Check if the user is logged in + if (this._authenticated) { + return of(true); + } + + // Check the access token availability + if (!this.accessToken) { + return of(false); + } + + // Check the access token expire date + if (AuthUtils.isTokenExpired(this.accessToken)) { + return of(false); + } + + // If the access token exists, and it didn't expire, sign in using it + return this.signInUsingToken(); + } +} diff --git a/src/app/core/auth/auth.utils.ts b/src/app/core/auth/auth.utils.ts new file mode 100644 index 0000000..e5a7e3e --- /dev/null +++ b/src/app/core/auth/auth.utils.ts @@ -0,0 +1,182 @@ +// ----------------------------------------------------------------------------------------------------- +// @ AUTH UTILITIES +// +// Methods are derivations of the Auth0 Angular-JWT helper service methods +// https://github.com/auth0/angular2-jwt +// ----------------------------------------------------------------------------------------------------- + +export class AuthUtils { + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Is token expired? + * + * @param token + * @param offsetSeconds + */ + static isTokenExpired(token: string, offsetSeconds?: number): boolean { + // Return if there is no token + if (!token || token === '') { + return true; + } + + // Get the expiration date + const date = this._getTokenExpirationDate(token); + + offsetSeconds = offsetSeconds || 0; + + if (date === null) { + return true; + } + + // Check if the token is expired + return !(date.valueOf() > new Date().valueOf() + offsetSeconds * 1000); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Base64 decoder + * Credits: https://github.com/atk + * + * @param str + * @private + */ + private static _b64decode(str: string): string { + const chars = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let output = ''; + + str = String(str).replace(/=+$/, ''); + + if (str.length % 4 === 1) { + throw new Error( + "'atob' failed: The string to be decoded is not correctly encoded." + ); + } + + /* eslint-disable */ + for ( + // initialize result and counters + let bc = 0, bs: any, buffer: any, idx = 0; + // get next character + (buffer = str.charAt(idx++)); + // character found in table? initialize bit storage and add its ascii value; + ~buffer && + ((bs = bc % 4 ? bs * 64 + buffer : buffer), + // and if not first of each 4 characters, + // convert the first 8 bits to one ascii character + bc++ % 4) + ? (output += String.fromCharCode(255 & (bs >> ((-2 * bc) & 6)))) + : 0 + ) { + // try to find character in table (0-63, not found => -1) + buffer = chars.indexOf(buffer); + } + /* eslint-enable */ + + return output; + } + + /** + * Base64 unicode decoder + * + * @param str + * @private + */ + private static _b64DecodeUnicode(str: any): string { + return decodeURIComponent( + Array.prototype.map + .call( + this._b64decode(str), + (c: any) => + '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + ) + .join('') + ); + } + + /** + * URL Base 64 decoder + * + * @param str + * @private + */ + private static _urlBase64Decode(str: string): string { + let output = str.replace(/-/g, '+').replace(/_/g, '/'); + switch (output.length % 4) { + case 0: { + break; + } + case 2: { + output += '=='; + break; + } + case 3: { + output += '='; + break; + } + default: { + throw Error('Illegal base64url string!'); + } + } + return this._b64DecodeUnicode(output); + } + + /** + * Decode token + * + * @param token + * @private + */ + private static _decodeToken(token: string): any { + // Return if there is no token + if (!token) { + return null; + } + + // Split the token + const parts = token.split('.'); + + if (parts.length !== 3) { + throw new Error( + "The inspected token doesn't appear to be a JWT. Check to make sure it has three parts and see https://jwt.io for more." + ); + } + + // Decode the token using the Base64 decoder + const decoded = this._urlBase64Decode(parts[1]); + + if (!decoded) { + throw new Error('Cannot decode the token.'); + } + + return JSON.parse(decoded); + } + + /** + * Get token expiration date + * + * @param token + * @private + */ + private static _getTokenExpirationDate(token: string): Date | null { + // Get the decoded token + const decodedToken = this._decodeToken(token); + + // Return if the decodedToken doesn't have an 'exp' field + if (!decodedToken.hasOwnProperty('exp')) { + return null; + } + + // Convert the expiration date + const date = new Date(0); + date.setUTCSeconds(decodedToken.exp); + + return date; + } +} diff --git a/src/app/core/auth/guards/auth.guard.ts b/src/app/core/auth/guards/auth.guard.ts new file mode 100644 index 0000000..e98780c --- /dev/null +++ b/src/app/core/auth/guards/auth.guard.ts @@ -0,0 +1,30 @@ +import { inject } from '@angular/core'; +import { CanActivateChildFn, CanActivateFn, Router } from '@angular/router'; +import { AuthService } from 'app/core/auth/auth.service'; +import { of, switchMap } from 'rxjs'; + +export const AuthGuard: CanActivateFn | CanActivateChildFn = (route, state) => { + const router: Router = inject(Router); + + // Check the authentication status + return inject(AuthService) + .check() + .pipe( + switchMap((authenticated) => { + // If the user is not authenticated... + if (!authenticated) { + // Redirect to the sign-in page with a redirectUrl param + const redirectURL = + state.url === '/sign-out' + ? '' + : `redirectURL=${state.url}`; + const urlTree = router.parseUrl(`sign-in?${redirectURL}`); + + return of(urlTree); + } + + // Allow the access + return of(true); + }) + ); +}; diff --git a/src/app/core/auth/guards/noAuth.guard.ts b/src/app/core/auth/guards/noAuth.guard.ts new file mode 100644 index 0000000..99b6af0 --- /dev/null +++ b/src/app/core/auth/guards/noAuth.guard.ts @@ -0,0 +1,26 @@ +import { inject } from '@angular/core'; +import { CanActivateChildFn, CanActivateFn, Router } from '@angular/router'; +import { AuthService } from 'app/core/auth/auth.service'; +import { of, switchMap } from 'rxjs'; + +export const NoAuthGuard: CanActivateFn | CanActivateChildFn = ( + route, + state +) => { + const router: Router = inject(Router); + + // Check the authentication status + return inject(AuthService) + .check() + .pipe( + switchMap((authenticated) => { + // If the user is authenticated... + if (authenticated) { + return of(router.parseUrl('')); + } + + // Allow the access + return of(true); + }) + ); +}; diff --git a/src/app/core/icons/icons.provider.ts b/src/app/core/icons/icons.provider.ts new file mode 100644 index 0000000..12f34ec --- /dev/null +++ b/src/app/core/icons/icons.provider.ts @@ -0,0 +1,17 @@ +import { + ENVIRONMENT_INITIALIZER, + EnvironmentProviders, + inject, + Provider, +} from '@angular/core'; +import { IconsService } from 'app/core/icons/icons.service'; + +export const provideIcons = (): Array => { + return [ + { + provide: ENVIRONMENT_INITIALIZER, + useValue: () => inject(IconsService), + multi: true, + }, + ]; +}; diff --git a/src/app/core/icons/icons.service.ts b/src/app/core/icons/icons.service.ts new file mode 100644 index 0000000..8f9b166 --- /dev/null +++ b/src/app/core/icons/icons.service.ts @@ -0,0 +1,55 @@ +import { inject, Injectable } from '@angular/core'; +import { MatIconRegistry } from '@angular/material/icon'; +import { DomSanitizer } from '@angular/platform-browser'; + +@Injectable({ providedIn: 'root' }) +export class IconsService { + /** + * Constructor + */ + constructor() { + const domSanitizer = inject(DomSanitizer); + const matIconRegistry = inject(MatIconRegistry); + + // Register icon sets + matIconRegistry.addSvgIconSet( + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/material-twotone.svg' + ) + ); + matIconRegistry.addSvgIconSetInNamespace( + 'mat_outline', + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/material-outline.svg' + ) + ); + matIconRegistry.addSvgIconSetInNamespace( + 'mat_solid', + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/material-solid.svg' + ) + ); + matIconRegistry.addSvgIconSetInNamespace( + 'feather', + domSanitizer.bypassSecurityTrustResourceUrl('icons/feather.svg') + ); + matIconRegistry.addSvgIconSetInNamespace( + 'heroicons_outline', + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/heroicons-outline.svg' + ) + ); + matIconRegistry.addSvgIconSetInNamespace( + 'heroicons_solid', + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/heroicons-solid.svg' + ) + ); + matIconRegistry.addSvgIconSetInNamespace( + 'heroicons_mini', + domSanitizer.bypassSecurityTrustResourceUrl( + 'icons/heroicons-mini.svg' + ) + ); + } +} diff --git a/src/app/core/navigation/navigation.service.ts b/src/app/core/navigation/navigation.service.ts new file mode 100644 index 0000000..ba31cde --- /dev/null +++ b/src/app/core/navigation/navigation.service.ts @@ -0,0 +1,37 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { Navigation } from 'app/core/navigation/navigation.types'; +import { Observable, ReplaySubject, tap } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class NavigationService { + private _httpClient = inject(HttpClient); + private _navigation: ReplaySubject = + new ReplaySubject(1); + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for navigation + */ + get navigation$(): Observable { + return this._navigation.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get all navigation data + */ + get(): Observable { + return this._httpClient.get('api/common/navigation').pipe( + tap((navigation) => { + this._navigation.next(navigation); + }) + ); + } +} diff --git a/src/app/core/navigation/navigation.types.ts b/src/app/core/navigation/navigation.types.ts new file mode 100644 index 0000000..2ae7d9e --- /dev/null +++ b/src/app/core/navigation/navigation.types.ts @@ -0,0 +1,8 @@ +import { AngorNavigationItem } from '@angor/components/navigation'; + +export interface Navigation { + compact: AngorNavigationItem[]; + default: AngorNavigationItem[]; + futuristic: AngorNavigationItem[]; + horizontal: AngorNavigationItem[]; +} diff --git a/src/app/core/transloco/transloco.http-loader.ts b/src/app/core/transloco/transloco.http-loader.ts new file mode 100644 index 0000000..dc9a629 --- /dev/null +++ b/src/app/core/transloco/transloco.http-loader.ts @@ -0,0 +1,19 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Translation, TranslocoLoader } from '@ngneat/transloco'; +import { Observable } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class TranslocoHttpLoader implements TranslocoLoader { + constructor(private readonly httpClient: HttpClient) {} + + /** + * Get translation data for the specified language + * + * @param lang Language code + */ + getTranslation(lang: string): Observable { + const translationUrl = `./i18n/${lang}.json`; + return this.httpClient.get(translationUrl); + } +} diff --git a/src/app/core/user/user.service.ts b/src/app/core/user/user.service.ts new file mode 100644 index 0000000..90c1bd2 --- /dev/null +++ b/src/app/core/user/user.service.ts @@ -0,0 +1,56 @@ +import { HttpClient } from '@angular/common/http'; +import { inject, Injectable } from '@angular/core'; +import { User } from 'app/core/user/user.types'; +import { map, Observable, ReplaySubject, tap } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class UserService { + private _httpClient = inject(HttpClient); + private _user: ReplaySubject = new ReplaySubject(1); + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Setter & getter for user + * + * @param value + */ + set user(value: User) { + // Store the value + this._user.next(value); + } + + get user$(): Observable { + return this._user.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get the current signed-in user data + */ + get(): Observable { + return this._httpClient.get('api/common/user').pipe( + tap((user) => { + this._user.next(user); + }) + ); + } + + /** + * Update the user + * + * @param user + */ + update(user: User): Observable { + return this._httpClient.patch('api/common/user', { user }).pipe( + map((response) => { + this._user.next(response); + }) + ); + } +} diff --git a/src/app/core/user/user.types.ts b/src/app/core/user/user.types.ts new file mode 100644 index 0000000..01e5d60 --- /dev/null +++ b/src/app/core/user/user.types.ts @@ -0,0 +1,7 @@ +export interface User { + id: string; + name: string; + email: string; + avatar?: string; + status?: string; +} diff --git a/src/app/layout/common/notifications/notifications.component.html b/src/app/layout/common/notifications/notifications.component.html new file mode 100644 index 0000000..5dc9658 --- /dev/null +++ b/src/app/layout/common/notifications/notifications.component.html @@ -0,0 +1,204 @@ + + + + + +
+ +
+
+ +
+
Notifications
+
+ +
+
+ + +
+ + @for ( + notification of notifications; + track trackByFn($index, notification) + ) { +
+ + @if (notification.link) { + + @if (!notification.useRouter) { + + + + } + + @if (notification.useRouter) { + + + + } + } + + + @if (!notification.link) { +
+ +
+ } + + +
+ + + + +
+
+ + + + + @if (notification.icon && !notification.image) { +
+ + +
+ } + + @if (notification.image) { + + } + +
+ @if (notification.title) { +
+ } + @if (notification.description) { +
+ } +
+ {{ notification.time | date: 'MMM dd, h:mm a' }} +
+
+
+ } + + + @if (!notifications || !notifications.length) { +
+
+ +
+
+ No notifications +
+
+ When you have notifications, they will appear here. +
+
+ } +
+
+
diff --git a/src/app/layout/common/notifications/notifications.component.ts b/src/app/layout/common/notifications/notifications.component.ts new file mode 100644 index 0000000..e7869fd --- /dev/null +++ b/src/app/layout/common/notifications/notifications.component.ts @@ -0,0 +1,237 @@ +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { TemplatePortal } from '@angular/cdk/portal'; +import { DatePipe, NgClass, NgTemplateOutlet } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + OnDestroy, + OnInit, + TemplateRef, + ViewChild, + ViewContainerRef, + ViewEncapsulation, +} from '@angular/core'; +import { MatButton, MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { RouterLink } from '@angular/router'; +import { NotificationsService } from 'app/layout/common/notifications/notifications.service'; +import { Notification } from 'app/layout/common/notifications/notifications.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'notifications', + templateUrl: './notifications.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'notifications', + standalone: true, + imports: [ + MatButtonModule, + MatIconModule, + MatTooltipModule, + NgClass, + NgTemplateOutlet, + RouterLink, + DatePipe, + ], +}) +export class NotificationsComponent implements OnInit, OnDestroy { + @ViewChild('notificationsOrigin') private _notificationsOrigin: MatButton; + @ViewChild('notificationsPanel') + private _notificationsPanel: TemplateRef; + + notifications: Notification[]; + unreadCount: number = 0; + private _overlayRef: OverlayRef; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _notificationsService: NotificationsService, + private _overlay: Overlay, + private _viewContainerRef: ViewContainerRef + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to notification changes + this._notificationsService.notifications$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((notifications: Notification[]) => { + // Load the notifications + this.notifications = notifications; + + // Calculate the unread count + this._calculateUnreadCount(); + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + + // Dispose the overlay + if (this._overlayRef) { + this._overlayRef.dispose(); + } + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Open the notifications panel + */ + openPanel(): void { + // Return if the notifications panel or its origin is not defined + if (!this._notificationsPanel || !this._notificationsOrigin) { + return; + } + + // Create the overlay if it doesn't exist + if (!this._overlayRef) { + this._createOverlay(); + } + + // Attach the portal to the overlay + this._overlayRef.attach( + new TemplatePortal(this._notificationsPanel, this._viewContainerRef) + ); + } + + /** + * Close the notifications panel + */ + closePanel(): void { + this._overlayRef.detach(); + } + + /** + * Mark all notifications as read + */ + markAllAsRead(): void { + // Mark all as read + this._notificationsService.markAllAsRead().subscribe(); + } + + /** + * Toggle read status of the given notification + */ + toggleRead(notification: Notification): void { + // Toggle the read status + notification.read = !notification.read; + + // Update the notification + this._notificationsService + .update(notification.id, notification) + .subscribe(); + } + + /** + * Delete the given notification + */ + delete(notification: Notification): void { + // Delete the notification + this._notificationsService.delete(notification.id).subscribe(); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Create the overlay + */ + private _createOverlay(): void { + // Create the overlay + this._overlayRef = this._overlay.create({ + hasBackdrop: true, + backdropClass: 'angor-backdrop-on-mobile', + scrollStrategy: this._overlay.scrollStrategies.block(), + positionStrategy: this._overlay + .position() + .flexibleConnectedTo( + this._notificationsOrigin._elementRef.nativeElement + ) + .withLockedPosition(true) + .withPush(true) + .withPositions([ + { + originX: 'start', + originY: 'bottom', + overlayX: 'start', + overlayY: 'top', + }, + { + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + }, + { + originX: 'end', + originY: 'bottom', + overlayX: 'end', + overlayY: 'top', + }, + { + originX: 'end', + originY: 'top', + overlayX: 'end', + overlayY: 'bottom', + }, + ]), + }); + + // Detach the overlay from the portal on backdrop click + this._overlayRef.backdropClick().subscribe(() => { + this._overlayRef.detach(); + }); + } + + /** + * Calculate the unread count + * + * @private + */ + private _calculateUnreadCount(): void { + let count = 0; + + if (this.notifications && this.notifications.length) { + count = this.notifications.filter( + (notification) => !notification.read + ).length; + } + + this.unreadCount = count; + } +} diff --git a/src/app/layout/common/notifications/notifications.service.ts b/src/app/layout/common/notifications/notifications.service.ts new file mode 100644 index 0000000..1e907d5 --- /dev/null +++ b/src/app/layout/common/notifications/notifications.service.ts @@ -0,0 +1,170 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Notification } from 'app/layout/common/notifications/notifications.types'; +import { map, Observable, ReplaySubject, switchMap, take, tap } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class NotificationsService { + private _notifications: ReplaySubject = new ReplaySubject< + Notification[] + >(1); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for notifications + */ + get notifications$(): Observable { + return this._notifications.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get all notifications + */ + getAll(): Observable { + return this._httpClient + .get('api/common/notifications') + .pipe( + tap((notifications) => { + this._notifications.next(notifications); + }) + ); + } + + /** + * Create a notification + * + * @param notification + */ + create(notification: Notification): Observable { + return this.notifications$.pipe( + take(1), + switchMap((notifications) => + this._httpClient + .post('api/common/notifications', { + notification, + }) + .pipe( + map((newNotification) => { + // Update the notifications with the new notification + this._notifications.next([ + ...notifications, + newNotification, + ]); + + // Return the new notification from observable + return newNotification; + }) + ) + ) + ); + } + + /** + * Update the notification + * + * @param id + * @param notification + */ + update(id: string, notification: Notification): Observable { + return this.notifications$.pipe( + take(1), + switchMap((notifications) => + this._httpClient + .patch('api/common/notifications', { + id, + notification, + }) + .pipe( + map((updatedNotification: Notification) => { + // Find the index of the updated notification + const index = notifications.findIndex( + (item) => item.id === id + ); + + // Update the notification + notifications[index] = updatedNotification; + + // Update the notifications + this._notifications.next(notifications); + + // Return the updated notification + return updatedNotification; + }) + ) + ) + ); + } + + /** + * Delete the notification + * + * @param id + */ + delete(id: string): Observable { + return this.notifications$.pipe( + take(1), + switchMap((notifications) => + this._httpClient + .delete('api/common/notifications', { + params: { id }, + }) + .pipe( + map((isDeleted: boolean) => { + // Find the index of the deleted notification + const index = notifications.findIndex( + (item) => item.id === id + ); + + // Delete the notification + notifications.splice(index, 1); + + // Update the notifications + this._notifications.next(notifications); + + // Return the deleted status + return isDeleted; + }) + ) + ) + ); + } + + /** + * Mark all notifications as read + */ + markAllAsRead(): Observable { + return this.notifications$.pipe( + take(1), + switchMap((notifications) => + this._httpClient + .get('api/common/notifications/mark-all-as-read') + .pipe( + map((isUpdated: boolean) => { + // Go through all notifications and set them as read + notifications.forEach((notification, index) => { + notifications[index].read = true; + }); + + // Update the notifications + this._notifications.next(notifications); + + // Return the updated status + return isUpdated; + }) + ) + ) + ); + } +} diff --git a/src/app/layout/common/notifications/notifications.types.ts b/src/app/layout/common/notifications/notifications.types.ts new file mode 100644 index 0000000..3ebf5b6 --- /dev/null +++ b/src/app/layout/common/notifications/notifications.types.ts @@ -0,0 +1,11 @@ +export interface Notification { + id: string; + icon?: string; + image?: string; + title?: string; + description?: string; + time: string; + link?: string; + useRouter?: boolean; + read: boolean; +} diff --git a/src/app/layout/common/quick-chat/quick-chat.component.html b/src/app/layout/common/quick-chat/quick-chat.component.html new file mode 100644 index 0000000..a14d4de --- /dev/null +++ b/src/app/layout/common/quick-chat/quick-chat.component.html @@ -0,0 +1,299 @@ +
+
+ +
+ + @if (!opened || (opened && !selectedChat)) { +
+
+ +
+
+ Team Chat +
+ +
+ } + + + @if (opened && selectedChat) { +
+
+ @if (chat.contact.avatar) { + Contact avatar + } + @if (!chat.contact.avatar) { +
+ {{ chat.contact.name.charAt(0) }} +
+ } +
+
+ {{ chat.contact.name }} +
+ +
+ } +
+ + +
+ +
+
+ @for (chat of chats; track trackByFn($index, chat)) { +
+
+ @if (chat.unreadCount > 0) { +
+ } + @if (chat.contact.avatar) { + Contact avatar + } + @if (!chat.contact.avatar) { +
+ {{ chat.contact.name.charAt(0) }} +
+ } +
+
+ } +
+
+ + +
+ @if (chat) { +
+
+ @for ( + message of chat.messages; + track trackByFn(i, message); + let i = $index; + let first = $first; + let last = $last + ) { + + @if ( + first || + (chat.messages[i - 1].createdAt + | date: 'd') !== + (message.createdAt | date: 'd') + ) { +
+
+
+ {{ + message.createdAt + | date: 'longDate' + }} +
+
+
+ } +
+ +
+ + @if ( + last || + chat.messages[i + 1].isMine !== + message.isMine + ) { +
+ +
+ } + +
+
+ + @if ( + first || + last || + chat.messages[i + 1].isMine !== + message.isMine || + chat.messages[i + 1].createdAt !== + message.createdAt + ) { +
+ {{ + message.createdAt + | date: 'HH:mm' + }} +
+ } +
+ } +
+
+ + +
+ + + +
+ +
+
+ } @else { +
+ +
+ Select a conversation +
+
+ } +
+
+
+
+ + + + + + + + + + + + diff --git a/src/app/layout/common/quick-chat/quick-chat.component.scss b/src/app/layout/common/quick-chat/quick-chat.component.scss new file mode 100644 index 0000000..e6aacc9 --- /dev/null +++ b/src/app/layout/common/quick-chat/quick-chat.component.scss @@ -0,0 +1,55 @@ +quick-chat { + z-index: 399; + + > div { + overflow: hidden; + } + + &.quick-chat-opened { + > div { + overflow: visible; + } + } + + &:not(.quick-chat-opened) { + > div { + overflow: visible; + animation: addOverflowHidden 1ms linear 400ms; + animation-fill-mode: forwards; + } + } +} + +/* Adjustments depending on the selected layout */ +.quick-chat-header { + height: 64px; + + enterprise-layout &, + modern-layout & { + height: 80px !important; + } +} + +/* Overlay */ +.quick-chat-overlay { + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 299; + opacity: 1; + background-color: transparent; +} + +@keyframes addOverflowHidden { + 0% { + overflow: visible; + } + 99% { + overflow: visible; + } + 100% { + overflow: hidden; + } +} diff --git a/src/app/layout/common/quick-chat/quick-chat.component.ts b/src/app/layout/common/quick-chat/quick-chat.component.ts new file mode 100644 index 0000000..fd71a80 --- /dev/null +++ b/src/app/layout/common/quick-chat/quick-chat.component.ts @@ -0,0 +1,329 @@ +import { ScrollStrategy, ScrollStrategyOptions } from '@angular/cdk/overlay'; +import { TextFieldModule } from '@angular/cdk/text-field'; +import { DOCUMENT, DatePipe, NgClass, NgTemplateOutlet } from '@angular/common'; +import { + AfterViewInit, + Component, + ElementRef, + HostBinding, + HostListener, + Inject, + NgZone, + OnDestroy, + OnInit, + Renderer2, + ViewChild, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { AngorScrollbarDirective } from '@angor/directives/scrollbar'; +import { QuickChatService } from 'app/layout/common/quick-chat/quick-chat.service'; +import { Chat } from 'app/layout/common/quick-chat/quick-chat.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'quick-chat', + templateUrl: './quick-chat.component.html', + styleUrls: ['./quick-chat.component.scss'], + encapsulation: ViewEncapsulation.None, + exportAs: 'quickChat', + standalone: true, + imports: [ + NgClass, + MatIconModule, + MatButtonModule, + AngorScrollbarDirective, + NgTemplateOutlet, + MatFormFieldModule, + MatInputModule, + TextFieldModule, + DatePipe, + ], +}) +export class QuickChatComponent implements OnInit, AfterViewInit, OnDestroy { + @ViewChild('messageInput') messageInput: ElementRef; + chat: Chat; + chats: Chat[]; + opened: boolean = false; + selectedChat: Chat; + private _mutationObserver: MutationObserver; + private _scrollStrategy: ScrollStrategy = + this._scrollStrategyOptions.block(); + private _overlay: HTMLElement; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + @Inject(DOCUMENT) private _document: Document, + private _elementRef: ElementRef, + private _renderer2: Renderer2, + private _ngZone: NgZone, + private _quickChatService: QuickChatService, + private _scrollStrategyOptions: ScrollStrategyOptions + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Decorated methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Host binding for component classes + */ + @HostBinding('class') get classList(): any { + return { + 'quick-chat-opened': this.opened, + }; + } + + /** + * Resize on 'input' and 'ngModelChange' events + * + * @private + */ + @HostListener('input') + @HostListener('ngModelChange') + private _resizeMessageInput(): void { + // This doesn't need to trigger Angular's change detection by itself + this._ngZone.runOutsideAngular(() => { + setTimeout(() => { + // Set the height to 'auto' so we can correctly read the scrollHeight + this.messageInput.nativeElement.style.height = 'auto'; + + // Get the scrollHeight and subtract the vertical padding + this.messageInput.nativeElement.style.height = `${this.messageInput.nativeElement.scrollHeight}px`; + }); + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Chat + this._quickChatService.chat$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chat: Chat) => { + this.chat = chat; + }); + + // Chats + this._quickChatService.chats$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chats: Chat[]) => { + this.chats = chats; + }); + + // Selected chat + this._quickChatService.chat$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((chat: Chat) => { + this.selectedChat = chat; + }); + } + + /** + * After view init + */ + ngAfterViewInit(): void { + // Fix for Firefox. + // + // Because 'position: sticky' doesn't work correctly inside a 'position: fixed' parent, + // adding the '.cdk-global-scrollblock' to the html element breaks the navigation's position. + // This fixes the problem by reading the 'top' value from the html element and adding it as a + // 'marginTop' to the navigation itself. + this._mutationObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + const mutationTarget = mutation.target as HTMLElement; + if (mutation.attributeName === 'class') { + if ( + mutationTarget.classList.contains( + 'cdk-global-scrollblock' + ) + ) { + const top = parseInt(mutationTarget.style.top, 10); + this._renderer2.setStyle( + this._elementRef.nativeElement, + 'margin-top', + `${Math.abs(top)}px` + ); + } else { + this._renderer2.setStyle( + this._elementRef.nativeElement, + 'margin-top', + null + ); + } + } + }); + }); + this._mutationObserver.observe(this._document.documentElement, { + attributes: true, + attributeFilter: ['class'], + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Disconnect the mutation observer + this._mutationObserver.disconnect(); + + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Open the panel + */ + open(): void { + // Return if the panel has already opened + if (this.opened) { + return; + } + + // Open the panel + this._toggleOpened(true); + } + + /** + * Close the panel + */ + close(): void { + // Return if the panel has already closed + if (!this.opened) { + return; + } + + // Close the panel + this._toggleOpened(false); + } + + /** + * Toggle the panel + */ + toggle(): void { + if (this.opened) { + this.close(); + } else { + this.open(); + } + } + + /** + * Select the chat + * + * @param id + */ + selectChat(id: string): void { + // Open the panel + this._toggleOpened(true); + + // Get the chat data + this._quickChatService.getChatById(id).subscribe(); + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Show the backdrop + * + * @private + */ + private _showOverlay(): void { + // Try hiding the overlay in case there is one already opened + this._hideOverlay(); + + // Create the backdrop element + this._overlay = this._renderer2.createElement('div'); + + // Return if overlay couldn't be create for some reason + if (!this._overlay) { + return; + } + + // Add a class to the backdrop element + this._overlay.classList.add('quick-chat-overlay'); + + // Append the backdrop to the parent of the panel + this._renderer2.appendChild( + this._elementRef.nativeElement.parentElement, + this._overlay + ); + + // Enable block scroll strategy + this._scrollStrategy.enable(); + + // Add an event listener to the overlay + this._overlay.addEventListener('click', () => { + this.close(); + }); + } + + /** + * Hide the backdrop + * + * @private + */ + private _hideOverlay(): void { + if (!this._overlay) { + return; + } + + // If the backdrop still exists... + if (this._overlay) { + // Remove the backdrop + this._overlay.parentNode.removeChild(this._overlay); + this._overlay = null; + } + + // Disable block scroll strategy + this._scrollStrategy.disable(); + } + + /** + * Open/close the panel + * + * @param open + * @private + */ + private _toggleOpened(open: boolean): void { + // Set the opened + this.opened = open; + + // If the panel opens, show the overlay + if (open) { + this._showOverlay(); + } + // Otherwise, hide the overlay + else { + this._hideOverlay(); + } + } +} diff --git a/src/app/layout/common/quick-chat/quick-chat.service.ts b/src/app/layout/common/quick-chat/quick-chat.service.ts new file mode 100644 index 0000000..c829838 --- /dev/null +++ b/src/app/layout/common/quick-chat/quick-chat.service.ts @@ -0,0 +1,84 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Chat } from 'app/layout/common/quick-chat/quick-chat.types'; +import { + BehaviorSubject, + map, + Observable, + of, + switchMap, + tap, + throwError, +} from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class QuickChatService { + private _chat: BehaviorSubject = new BehaviorSubject(null); + private _chats: BehaviorSubject = new BehaviorSubject(null); + + /** + * Constructor + */ + constructor(private _httpClient: HttpClient) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for chat + */ + get chat$(): Observable { + return this._chat.asObservable(); + } + + /** + * Getter for chat + */ + get chats$(): Observable { + return this._chats.asObservable(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Get chats + */ + getChats(): Observable { + return this._httpClient.get('api/apps/chat/chats').pipe( + tap((response: Chat[]) => { + this._chats.next(response); + }) + ); + } + + /** + * Get chat + * + * @param id + */ + getChatById(id: string): Observable { + return this._httpClient + .get('api/apps/chat/chat', { params: { id } }) + .pipe( + map((chat) => { + // Update the chat + this._chat.next(chat); + + // Return the chat + return chat; + }), + switchMap((chat) => { + if (!chat) { + return throwError( + 'Could not found chat with id of ' + id + '!' + ); + } + + return of(chat); + }) + ); + } +} diff --git a/src/app/layout/common/quick-chat/quick-chat.types.ts b/src/app/layout/common/quick-chat/quick-chat.types.ts new file mode 100644 index 0000000..dc8142f --- /dev/null +++ b/src/app/layout/common/quick-chat/quick-chat.types.ts @@ -0,0 +1,44 @@ +export interface Chat { + id?: string; + contactId?: string; + contact?: Contact; + unreadCount?: number; + muted?: boolean; + lastMessage?: string; + lastMessageAt?: string; + messages?: { + id?: string; + chatId?: string; + contactId?: string; + isMine?: boolean; + value?: string; + createdAt?: string; + }[]; +} + +export interface Contact { + id?: string; + avatar?: string; + name?: string; + about?: string; + details?: { + emails?: { + email?: string; + label?: string; + }[]; + phoneNumbers?: { + country?: string; + phoneNumber?: string; + label?: string; + }[]; + title?: string; + company?: string; + birthday?: string; + address?: string; + }; + attachments?: { + media?: any[]; + docs?: any[]; + links?: any[]; + }; +} diff --git a/src/app/layout/common/search/search.component.html b/src/app/layout/common/search/search.component.html new file mode 100644 index 0000000..04e4ed5 --- /dev/null +++ b/src/app/layout/common/search/search.component.html @@ -0,0 +1,234 @@ + +@if (appearance === 'bar') { + @if (!opened) { + + } + @if (opened) { +
+ + + + @if (resultSets && !resultSets.length) { + + No results found! + + } + @for ( + resultSet of resultSets; + track trackByFn($index, resultSet) + ) { + + {{ resultSet.label.toUpperCase() }} + + @for ( + result of resultSet.results; + track trackByFn($index, result) + ) { + + + @if (resultSet.id === 'contacts') { + + } + + @if (resultSet.id === 'pages') { + + } + + @if (resultSet.id === 'tasks') { + + } + + } + } + + +
+ } +} + + +@if (appearance === 'basic') { +
+ + + + + + @if (resultSets && !resultSets.length) { + + No results found! + + } + @for (resultSet of resultSets; track trackByFn($index, resultSet)) { + + {{ resultSet.label.toUpperCase() }} + + @for ( + result of resultSet.results; + track trackByFn($index, result) + ) { + + + @if (resultSet.id === 'contacts') { + + } + + @if (resultSet.id === 'pages') { + + } + + @if (resultSet.id === 'tasks') { + + } + + } + } + +
+} + + + +
+
+ @if (result.avatar) { + + } + @if (!result.avatar) { + + } +
+
+ +
+
+
+ + + +
+
+
+ {{ result.link }} +
+
+
+ + + +
+ @if (result.completed) { + + } + @if (!result.completed) { + + } +
+
+
diff --git a/src/app/layout/common/search/search.component.ts b/src/app/layout/common/search/search.component.ts new file mode 100644 index 0000000..8bf23d3 --- /dev/null +++ b/src/app/layout/common/search/search.component.ts @@ -0,0 +1,256 @@ +import { Overlay } from '@angular/cdk/overlay'; +import { NgClass, NgTemplateOutlet } from '@angular/common'; +import { HttpClient } from '@angular/common/http'; +import { + Component, + ElementRef, + EventEmitter, + HostBinding, + Input, + OnChanges, + OnDestroy, + OnInit, + Output, + Renderer2, + SimpleChanges, + ViewChild, + ViewEncapsulation, + inject, +} from '@angular/core'; +import { + FormsModule, + ReactiveFormsModule, + UntypedFormControl, +} from '@angular/forms'; +import { + MAT_AUTOCOMPLETE_SCROLL_STRATEGY, + MatAutocomplete, + MatAutocompleteModule, +} from '@angular/material/autocomplete'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; +import { RouterLink } from '@angular/router'; +import { angorAnimations } from '@angor/animations/public-api'; +import { Subject, debounceTime, filter, map, takeUntil } from 'rxjs'; + +@Component({ + selector: 'search', + templateUrl: './search.component.html', + encapsulation: ViewEncapsulation.None, + exportAs: 'angorSearch', + animations: angorAnimations, + standalone: true, + imports: [ + MatButtonModule, + MatIconModule, + FormsModule, + MatAutocompleteModule, + ReactiveFormsModule, + MatOptionModule, + RouterLink, + NgTemplateOutlet, + MatFormFieldModule, + MatInputModule, + NgClass, + ], + providers: [ + { + provide: MAT_AUTOCOMPLETE_SCROLL_STRATEGY, + useFactory: () => { + const overlay = inject(Overlay); + return () => overlay.scrollStrategies.block(); + }, + }, + ], +}) +export class SearchComponent implements OnChanges, OnInit, OnDestroy { + @Input() appearance: 'basic' | 'bar' = 'basic'; + @Input() debounce: number = 300; + @Input() minLength: number = 2; + @Output() search: EventEmitter = new EventEmitter(); + + opened: boolean = false; + resultSets: any[]; + searchControl: UntypedFormControl = new UntypedFormControl(); + private _matAutocomplete: MatAutocomplete; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _elementRef: ElementRef, + private _httpClient: HttpClient, + private _renderer2: Renderer2 + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Host binding for component classes + */ + @HostBinding('class') get classList(): any { + return { + 'search-appearance-bar': this.appearance === 'bar', + 'search-appearance-basic': this.appearance === 'basic', + 'search-opened': this.opened, + }; + } + + /** + * Setter for bar search input + * + * @param value + */ + @ViewChild('barSearchInput') + set barSearchInput(value: ElementRef) { + // If the value exists, it means that the search input + // is now in the DOM, and we can focus on the input.. + if (value) { + // Give Angular time to complete the change detection cycle + setTimeout(() => { + // Focus to the input element + value.nativeElement.focus(); + }); + } + } + + /** + * Setter for mat-autocomplete element reference + * + * @param value + */ + @ViewChild('matAutocomplete') + set matAutocomplete(value: MatAutocomplete) { + this._matAutocomplete = value; + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On changes + * + * @param changes + */ + ngOnChanges(changes: SimpleChanges): void { + // Appearance + if ('appearance' in changes) { + // To prevent any issues, close the + // search after changing the appearance + this.close(); + } + } + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to the search field value changes + this.searchControl.valueChanges + .pipe( + debounceTime(this.debounce), + takeUntil(this._unsubscribeAll), + map((value) => { + // Set the resultSets to null if there is no value or + // the length of the value is smaller than the minLength + // so the autocomplete panel can be closed + if (!value || value.length < this.minLength) { + this.resultSets = null; + } + + // Continue + return value; + }), + // Filter out undefined/null/false statements and also + // filter out the values that are smaller than minLength + filter((value) => value && value.length >= this.minLength) + ) + .subscribe((value) => { + this._httpClient + .post('api/common/search', { query: value }) + .subscribe((resultSets: any) => { + // Store the result sets + this.resultSets = resultSets; + + // Execute the event + this.search.next(resultSets); + }); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * On keydown of the search input + * + * @param event + */ + onKeydown(event: KeyboardEvent): void { + // Escape + if (event.code === 'Escape') { + // If the appearance is 'bar' and the mat-autocomplete is not open, close the search + if (this.appearance === 'bar' && !this._matAutocomplete.isOpen) { + this.close(); + } + } + } + + /** + * Open the search + * Used in 'bar' + */ + open(): void { + // Return if it's already opened + if (this.opened) { + return; + } + + // Open the search + this.opened = true; + } + + /** + * Close the search + * * Used in 'bar' + */ + close(): void { + // Return if it's already closed + if (!this.opened) { + return; + } + + // Clear the search input + this.searchControl.setValue(''); + + // Close the search + this.opened = false; + } + + /** + * Track by function for ngFor loops + * + * @param index + * @param item + */ + trackByFn(index: number, item: any): any { + return item.id || index; + } +} diff --git a/src/app/layout/common/settings/settings.component.html b/src/app/layout/common/settings/settings.component.html new file mode 100644 index 0000000..7937ee6 --- /dev/null +++ b/src/app/layout/common/settings/settings.component.html @@ -0,0 +1,744 @@ +
+ +
+ + +
+
+ +
+ Settings +
+ +
+ +
+ +
THEME
+
+ @for (theme of config.themes; track theme) { +
+
+
+ {{ theme.name }} +
+
+ } +
+ +
+ + +
SCHEME
+
+ +
+
+ +
+
+ Auto +
+
+ +
+
+ +
+
+ Dark +
+
+ +
+
+ +
+
+ Light +
+
+
+ +
+ + +
LAYOUT
+
+ +
+
+
+
+
+ Empty +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classic +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Classy +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Compact +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dense +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Futuristic +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Thin +
+
+ +
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Centered +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Enterprise +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Material +
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Modern +
+
+
+
+
+
diff --git a/src/app/layout/common/settings/settings.component.ts b/src/app/layout/common/settings/settings.component.ts new file mode 100644 index 0000000..da6f546 --- /dev/null +++ b/src/app/layout/common/settings/settings.component.ts @@ -0,0 +1,131 @@ +import { NgClass } from '@angular/common'; +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatTooltipModule } from '@angular/material/tooltip'; +import { Router } from '@angular/router'; +import { AngorDrawerComponent } from '@angor/components/drawer'; +import { + AngorConfig, + AngorConfigService, + Scheme, + Theme, + Themes, +} from '@angor/services/config'; + +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'settings', + templateUrl: './settings.component.html', + styles: [ + ` + settings { + position: static; + display: block; + flex: none; + width: auto; + } + + @media (screen and min-width: 1280px) { + empty-layout + settings .settings-cog { + right: 0 !important; + } + } + `, + ], + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ + MatIconModule, + AngorDrawerComponent, + MatButtonModule, + NgClass, + MatTooltipModule, + ], +}) +export class SettingsComponent implements OnInit, OnDestroy { + config: AngorConfig; + layout: string; + scheme: 'dark' | 'light'; + theme: string; + themes: Themes; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _router: Router, + private _angorConfigService: AngorConfigService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to config changes + this._angorConfigService.config$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((config: AngorConfig) => { + localStorage.setItem('angorConfig', JSON.stringify(config)); + + this.config = config; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Set the layout on the config + * + * @param layout + */ + setLayout(layout: string): void { + // Clear the 'layout' query param to allow layout changes + this._router + .navigate([], { + queryParams: { + layout: null, + }, + queryParamsHandling: 'merge', + }) + .then(() => { + // Set the config + this._angorConfigService.config = { layout }; + }); + } + + /** + * Set the scheme on the config + * + * @param scheme + */ + setScheme(scheme: Scheme): void { + this._angorConfigService.config = { scheme }; + } + + /** + * Set the theme on the config + * + * @param theme + */ + setTheme(theme: Theme): void { + this._angorConfigService.config = { theme }; + } +} diff --git a/src/app/layout/common/user/user.component.html b/src/app/layout/common/user/user.component.html new file mode 100644 index 0000000..cbdd0e9 --- /dev/null +++ b/src/app/layout/common/user/user.component.html @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/layout/common/user/user.component.ts b/src/app/layout/common/user/user.component.ts new file mode 100644 index 0000000..28f0618 --- /dev/null +++ b/src/app/layout/common/user/user.component.ts @@ -0,0 +1,166 @@ +import { AngorConfig, AngorConfigService, Scheme, Theme, Themes } from '@angor/services/config'; +import { BooleanInput } from '@angular/cdk/coercion'; +import { NgClass } from '@angular/common'; +import { + ChangeDetectionStrategy, + ChangeDetectorRef, + Component, + Input, + OnDestroy, + OnInit, + ViewEncapsulation, +} from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatDividerModule } from '@angular/material/divider'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { Router } from '@angular/router'; +import { UserService } from 'app/core/user/user.service'; +import { User } from 'app/core/user/user.types'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'user', + templateUrl: './user.component.html', + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, + exportAs: 'user', + standalone: true, + imports: [ + MatButtonModule, + MatMenuModule, + MatIconModule, + NgClass, + MatDividerModule, + ], +}) +export class UserComponent implements OnInit, OnDestroy { + /* eslint-disable @typescript-eslint/naming-convention */ + static ngAcceptInputType_showAvatar: BooleanInput; + /* eslint-enable @typescript-eslint/naming-convention */ + + @Input() showAvatar: boolean = true; + user: User; + + private _unsubscribeAll: Subject = new Subject(); + config: AngorConfig; + layout: string; + scheme: 'dark' | 'light'; + theme: string; + themes: Themes; + /** + * Constructor + */ + constructor( + private _changeDetectorRef: ChangeDetectorRef, + private _router: Router, + private _userService: UserService, + private _angorConfigService: AngorConfigService + + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to user changes + this._userService.user$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((user: User) => { + this.user = user; + + // Mark for check + this._changeDetectorRef.markForCheck(); + }); + + this._angorConfigService.config$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((config: AngorConfig) => { + localStorage.setItem('angorConfig', JSON.stringify(config)); + + this.config = config; + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Update the user status + * + * @param status + */ + updateUserStatus(status: string): void { + // Return if user is not available + if (!this.user) { + return; + } + + // Update the user + this._userService + .update({ + ...this.user, + status, + }) + .subscribe(); + } + + /** + * Sign out + */ + signOut(): void { + this._router.navigate(['/sign-out']); + } + + /** + * Set the layout on the config + * + * @param layout + */ + setLayout(layout: string): void { + // Clear the 'layout' query param to allow layout changes + this._router + .navigate([], { + queryParams: { + layout: null, + }, + queryParamsHandling: 'merge', + }) + .then(() => { + // Set the config + this._angorConfigService.config = { layout }; + }); + } + + /** + * Set the scheme on the config + * + * @param scheme + */ + setScheme(scheme: Scheme): void { + this._angorConfigService.config = { scheme }; + } + + /** + * Set the theme on the config + * + * @param theme + */ + setTheme(theme: Theme): void { + this._angorConfigService.config = { theme }; + } +} diff --git a/src/app/layout/layout.component.html b/src/app/layout/layout.component.html new file mode 100644 index 0000000..e571288 --- /dev/null +++ b/src/app/layout/layout.component.html @@ -0,0 +1,11 @@ + +@if (layout === 'modern') { + +} + + + +@if (layout === 'classic') { + +} + diff --git a/src/app/layout/layout.component.scss b/src/app/layout/layout.component.scss new file mode 100644 index 0000000..06b3410 --- /dev/null +++ b/src/app/layout/layout.component.scss @@ -0,0 +1,25 @@ +layout { + display: flex; + flex: 1 1 auto; + width: 100%; + max-width: 100%; + min-width: 0; + + /* Base styles for individual layouts */ + > * { + position: relative; + display: flex; + flex: 1 1 auto; + width: 100%; + } + + /* Base styles for components that load as a route */ + router-outlet { + + * { + position: relative; + display: flex; + flex: 1 1 auto; + width: 100%; + } + } +} diff --git a/src/app/layout/layout.component.ts b/src/app/layout/layout.component.ts new file mode 100644 index 0000000..91c7b9d --- /dev/null +++ b/src/app/layout/layout.component.ts @@ -0,0 +1,236 @@ +import { DOCUMENT } from '@angular/common'; +import { + Component, + Inject, + OnDestroy, + OnInit, + Renderer2, + ViewEncapsulation, +} from '@angular/core'; +import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; +import { AngorConfig, AngorConfigService } from '@angor/services/config'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { AngorPlatformService } from '@angor/services/platform'; +import { ANGOR_VERSION } from '@angor/version'; +import { Subject, combineLatest, filter, map, takeUntil } from 'rxjs'; +import { SettingsComponent } from './common/settings/settings.component'; +import { EmptyLayoutComponent } from './layouts/empty/empty.component'; +import { ModernLayoutComponent } from './layouts/horizontal/modern/modern.component'; +import { ClassicLayoutComponent } from './layouts/vertical/classic/classic.component'; + + +@Component({ + selector: 'layout', + templateUrl: './layout.component.html', + styleUrls: ['./layout.component.scss'], + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ + EmptyLayoutComponent, + ModernLayoutComponent, + ClassicLayoutComponent, + SettingsComponent, + ], +}) +export class LayoutComponent implements OnInit, OnDestroy { + config: AngorConfig; + layout: string; + scheme: 'dark' | 'light'; + theme: string; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _activatedRoute: ActivatedRoute, + @Inject(DOCUMENT) private _document: any, + private _renderer2: Renderer2, + private _router: Router, + private _angorConfigService: AngorConfigService, + private _angorMediaWatcherService: AngorMediaWatcherService, + private _angorPlatformService: AngorPlatformService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Set the theme and scheme based on the configuration + combineLatest([ + this._angorConfigService.config$, + this._angorMediaWatcherService.onMediaQueryChange$([ + '(prefers-color-scheme: dark)', + '(prefers-color-scheme: light)', + ]), + ]) + .pipe( + takeUntil(this._unsubscribeAll), + map(([config, mql]) => { + const options = { + scheme: config.scheme, + theme: config.theme, + }; + + // If the scheme is set to 'auto'... + if (config.scheme === 'auto') { + // Decide the scheme using the media query + options.scheme = mql.breakpoints[ + '(prefers-color-scheme: dark)' + ] + ? 'dark' + : 'light'; + } + + return options; + }) + ) + .subscribe((options) => { + // Store the options + this.scheme = options.scheme; + this.theme = options.theme; + + // Update the scheme and theme + this._updateScheme(); + this._updateTheme(); + }); + + // Subscribe to config changes + this._angorConfigService.config$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((config: AngorConfig) => { + // Store the config + this.config = config; + + // Update the layout + this._updateLayout(); + }); + + // Subscribe to NavigationEnd event + this._router.events + .pipe( + filter((event) => event instanceof NavigationEnd), + takeUntil(this._unsubscribeAll) + ) + .subscribe(() => { + // Update the layout + this._updateLayout(); + }); + + // Set the app version + this._renderer2.setAttribute( + this._document.querySelector('[ng-version]'), + 'angor-version', + ANGOR_VERSION + ); + + // Set the OS name + this._renderer2.addClass( + this._document.body, + this._angorPlatformService.osName + ); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Update the selected layout + */ + private _updateLayout(): void { + // Get the current activated route + let route = this._activatedRoute; + while (route.firstChild) { + route = route.firstChild; + } + + // 1. Set the layout from the config + this.layout = this.config.layout; + + // 2. Get the query parameter from the current route and + // set the layout and save the layout to the config + const layoutFromQueryParam = route.snapshot.queryParamMap.get('layout'); + if (layoutFromQueryParam) { + this.layout = layoutFromQueryParam; + if (this.config) { + this.config.layout = layoutFromQueryParam; + } + } + + // 3. Iterate through the paths and change the layout as we find + // a config for it. + // + // The reason we do this is that there might be empty grouping + // paths or componentless routes along the path. Because of that, + // we cannot just assume that the layout configuration will be + // in the last path's config or in the first path's config. + // + // So, we get all the paths that matched starting from root all + // the way to the current activated route, walk through them one + // by one and change the layout as we find the layout config. This + // way, layout configuration can live anywhere within the path and + // we won't miss it. + // + // Also, this will allow overriding the layout in any time so we + // can have different layouts for different routes. + const paths = route.pathFromRoot; + paths.forEach((path) => { + // Check if there is a 'layout' data + if ( + path.routeConfig && + path.routeConfig.data && + path.routeConfig.data.layout + ) { + // Set the layout + this.layout = path.routeConfig.data.layout; + } + }); + } + + /** + * Update the selected scheme + * + * @private + */ + private _updateScheme(): void { + // Remove class names for all schemes + this._document.body.classList.remove('light', 'dark'); + + // Add class name for the currently selected scheme + this._document.body.classList.add(this.scheme); + } + + /** + * Update the selected theme + * + * @private + */ + private _updateTheme(): void { + // Find the class name for the previously selected theme and remove it + this._document.body.classList.forEach((className: string) => { + if (className.startsWith('theme-')) { + this._document.body.classList.remove( + className, + className.split('-')[1] + ); + } + }); + + // Add class name for the currently selected theme + this._document.body.classList.add(this.theme); + } +} diff --git a/src/app/layout/layouts/empty/empty.component.html b/src/app/layout/layouts/empty/empty.component.html new file mode 100644 index 0000000..7b52c5b --- /dev/null +++ b/src/app/layout/layouts/empty/empty.component.html @@ -0,0 +1,14 @@ + + + + +
+ +
+ + @if (true) { + + } +
+
diff --git a/src/app/layout/layouts/empty/empty.component.ts b/src/app/layout/layouts/empty/empty.component.ts new file mode 100644 index 0000000..93d19dd --- /dev/null +++ b/src/app/layout/layouts/empty/empty.component.ts @@ -0,0 +1,33 @@ +import { Component, OnDestroy, ViewEncapsulation } from '@angular/core'; +import { RouterOutlet } from '@angular/router'; +import { AngorLoadingBarComponent } from '@angor/components/loading-bar'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'empty-layout', + templateUrl: './empty.component.html', + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [AngorLoadingBarComponent, RouterOutlet], +}) +export class EmptyLayoutComponent implements OnDestroy { + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor() {} + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } +} diff --git a/src/app/layout/layouts/horizontal/modern/modern.component.html b/src/app/layout/layouts/horizontal/modern/modern.component.html new file mode 100644 index 0000000..9fd4e44 --- /dev/null +++ b/src/app/layout/layouts/horizontal/modern/modern.component.html @@ -0,0 +1,103 @@ + + + + +@if (isScreenSmall) { + + + + +
+ Angor Hub +
+
+
+} + + +
+ +
+ @if (!isScreenSmall) { + +
+ + +
+ + + } + + @if (isScreenSmall) { + + } + +
+ + + + + + +
+
+ + +
+ + @if (true) { + + } +
+ + +
+ Angor © {{ currentYear }} +
+
+ + + diff --git a/src/app/layout/layouts/horizontal/modern/modern.component.ts b/src/app/layout/layouts/horizontal/modern/modern.component.ts new file mode 100644 index 0000000..99ccd71 --- /dev/null +++ b/src/app/layout/layouts/horizontal/modern/modern.component.ts @@ -0,0 +1,121 @@ +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { AngorFullscreenComponent } from '@angor/components/fullscreen'; +import { AngorLoadingBarComponent } from '@angor/components/loading-bar'; +import { + AngorHorizontalNavigationComponent, + AngorNavigationService, + AngorVerticalNavigationComponent, +} from '@angor/components/navigation'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { NavigationService } from 'app/core/navigation/navigation.service'; +import { Navigation } from 'app/core/navigation/navigation.types'; +import { NotificationsComponent } from 'app/layout/common/notifications/notifications.component'; +import { QuickChatComponent } from 'app/layout/common/quick-chat/quick-chat.component'; +import { SearchComponent } from 'app/layout/common/search/search.component'; +import { UserComponent } from 'app/layout/common/user/user.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'modern-layout', + templateUrl: './modern.component.html', + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ + AngorLoadingBarComponent, + AngorVerticalNavigationComponent, + AngorHorizontalNavigationComponent, + MatButtonModule, + MatIconModule, + AngorFullscreenComponent, + SearchComponent, + NotificationsComponent, + UserComponent, + RouterOutlet, + QuickChatComponent, + ], +}) +export class ModernLayoutComponent implements OnInit, OnDestroy { + isScreenSmall: boolean; + navigation: Navigation; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _activatedRoute: ActivatedRoute, + private _router: Router, + private _navigationService: NavigationService, + private _angorMediaWatcherService: AngorMediaWatcherService, + private _angorNavigationService: AngorNavigationService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for current year + */ + get currentYear(): number { + return new Date().getFullYear(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to navigation data + this._navigationService.navigation$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((navigation: Navigation) => { + this.navigation = navigation; + }); + + // Subscribe to media changes + this._angorMediaWatcherService.onMediaChange$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(({ matchingAliases }) => { + // Check if the screen is small + this.isScreenSmall = !matchingAliases.includes('md'); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle navigation + * + * @param name + */ + toggleNavigation(name: string): void { + // Get the navigation + const navigation = + this._angorNavigationService.getComponent( + name + ); + + if (navigation) { + // Toggle the opened status + navigation.toggle(); + } + } +} diff --git a/src/app/layout/layouts/vertical/classic/classic.component.html b/src/app/layout/layouts/vertical/classic/classic.component.html new file mode 100644 index 0000000..59a9b75 --- /dev/null +++ b/src/app/layout/layouts/vertical/classic/classic.component.html @@ -0,0 +1,81 @@ + + + + + + + + +
+ + Angor Hub + + +
+
+
+ + +
+ +
+ + + +
+ + + + + + +
+
+ + +
+ + @if (true) { + + } +
+ + +
+ Angor © {{ currentYear }} +
+
+ + + diff --git a/src/app/layout/layouts/vertical/classic/classic.component.ts b/src/app/layout/layouts/vertical/classic/classic.component.ts new file mode 100644 index 0000000..43d3e4f --- /dev/null +++ b/src/app/layout/layouts/vertical/classic/classic.component.ts @@ -0,0 +1,119 @@ +import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { ActivatedRoute, Router, RouterOutlet } from '@angular/router'; +import { AngorFullscreenComponent } from '@angor/components/fullscreen'; +import { AngorLoadingBarComponent } from '@angor/components/loading-bar'; +import { + AngorNavigationService, + AngorVerticalNavigationComponent, +} from '@angor/components/navigation'; +import { AngorMediaWatcherService } from '@angor/services/media-watcher'; +import { NavigationService } from 'app/core/navigation/navigation.service'; +import { Navigation } from 'app/core/navigation/navigation.types'; +import { NotificationsComponent } from 'app/layout/common/notifications/notifications.component'; +import { QuickChatComponent } from 'app/layout/common/quick-chat/quick-chat.component'; +import { SearchComponent } from 'app/layout/common/search/search.component'; +import { UserComponent } from 'app/layout/common/user/user.component'; +import { Subject, takeUntil } from 'rxjs'; + +@Component({ + selector: 'classic-layout', + templateUrl: './classic.component.html', + encapsulation: ViewEncapsulation.None, + standalone: true, + imports: [ + AngorLoadingBarComponent, + AngorVerticalNavigationComponent, + MatButtonModule, + MatIconModule, + AngorFullscreenComponent, + SearchComponent, + NotificationsComponent, + UserComponent, + RouterOutlet, + QuickChatComponent, + ], +}) +export class ClassicLayoutComponent implements OnInit, OnDestroy { + isScreenSmall: boolean; + navigation: Navigation; + private _unsubscribeAll: Subject = new Subject(); + + /** + * Constructor + */ + constructor( + private _activatedRoute: ActivatedRoute, + private _router: Router, + private _navigationService: NavigationService, + private _angorMediaWatcherService: AngorMediaWatcherService, + private _angorNavigationService: AngorNavigationService + ) {} + + // ----------------------------------------------------------------------------------------------------- + // @ Accessors + // ----------------------------------------------------------------------------------------------------- + + /** + * Getter for current year + */ + get currentYear(): number { + return new Date().getFullYear(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Lifecycle hooks + // ----------------------------------------------------------------------------------------------------- + + /** + * On init + */ + ngOnInit(): void { + // Subscribe to navigation data + this._navigationService.navigation$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe((navigation: Navigation) => { + this.navigation = navigation; + }); + + // Subscribe to media changes + this._angorMediaWatcherService.onMediaChange$ + .pipe(takeUntil(this._unsubscribeAll)) + .subscribe(({ matchingAliases }) => { + // Check if the screen is small + this.isScreenSmall = !matchingAliases.includes('md'); + }); + } + + /** + * On destroy + */ + ngOnDestroy(): void { + // Unsubscribe from all subscriptions + this._unsubscribeAll.next(null); + this._unsubscribeAll.complete(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Toggle navigation + * + * @param name + */ + toggleNavigation(name: string): void { + // Get the navigation + const navigation = + this._angorNavigationService.getComponent( + name + ); + + if (navigation) { + // Toggle the opened status + navigation.toggle(); + } + } +} diff --git a/src/app/mock-api/apps/academy/api.ts b/src/app/mock-api/apps/academy/api.ts new file mode 100644 index 0000000..164e604 --- /dev/null +++ b/src/app/mock-api/apps/academy/api.ts @@ -0,0 +1,79 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; +import { + categories as categoriesData, + courses as coursesData, + demoCourseSteps as demoCourseStepsData, +} from 'app/mock-api/apps/academy/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class AcademyMockApi { + private _categories: any[] = categoriesData; + private _courses: any[] = coursesData; + private _demoCourseSteps: any[] = demoCourseStepsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Categories - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/academy/categories') + .reply(() => { + // Clone the categories + const categories = cloneDeep(this._categories); + + // Sort the categories alphabetically by title + categories.sort((a, b) => a.title.localeCompare(b.title)); + + return [200, categories]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Courses - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/academy/courses').reply(() => { + // Clone the courses + const courses = cloneDeep(this._courses); + + return [200, courses]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Course - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/academy/courses/course') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the courses and steps + const courses = cloneDeep(this._courses); + const steps = cloneDeep(this._demoCourseSteps); + + // Find the course and attach steps to it + const course = courses.find((item) => item.id === id); + if (course) { + course.steps = steps; + } + + return [200, course]; + }); + } +} diff --git a/src/app/mock-api/apps/academy/data.ts b/src/app/mock-api/apps/academy/data.ts new file mode 100644 index 0000000..fcc09cf --- /dev/null +++ b/src/app/mock-api/apps/academy/data.ts @@ -0,0 +1,724 @@ +/* eslint-disable */ +export const categories = [ + { + id: '9a67dff7-3c38-4052-a335-0cef93438ff6', + title: 'Web', + slug: 'web', + }, + { + id: 'a89672f5-e00d-4be4-9194-cb9d29f82165', + title: 'Firebase', + slug: 'firebase', + }, + { + id: '02f42092-bb23-4552-9ddb-cfdcc235d48f', + title: 'Cloud', + slug: 'cloud', + }, + { + id: '5648a630-979f-4403-8c41-fc9790dea8cd', + title: 'Android', + slug: 'android', + }, +]; +export const courses = [ + { + id: '694e4e5f-f25f-470b-bd0e-26b1d4f64028', + title: 'Basics of Angular', + slug: 'basics-of-angular', + description: 'Introductory course for Angular and framework basics', + category: 'web', + duration: 30, + totalSteps: 11, + updatedAt: 'Jun 28, 2021', + featured: true, + progress: { + currentStep: 3, + completed: 2, + }, + }, + { + id: 'f924007a-2ee9-470b-a316-8d21ed78277f', + title: 'Basics of TypeScript', + slug: 'basics-of-typeScript', + description: 'Beginner course for Typescript and its basics', + category: 'web', + duration: 60, + totalSteps: 11, + updatedAt: 'Nov 01, 2021', + featured: true, + progress: { + currentStep: 5, + completed: 3, + }, + }, + { + id: '0c06e980-abb5-4ba7-ab65-99a228cab36b', + title: 'Android N: Quick Settings', + slug: 'android-n-quick-settings', + description: 'Step by step guide for Android N: Quick Settings', + category: 'android', + duration: 120, + totalSteps: 11, + updatedAt: 'May 08, 2021', + featured: false, + progress: { + currentStep: 10, + completed: 1, + }, + }, + { + id: '1b9a9acc-9a36-403e-a1e7-b11780179e38', + title: 'Build an App for the Google Assistant with Firebase', + slug: 'build-an-app-for-the-google-assistant-with-firebase', + description: 'Dive deep into Google Assistant apps using Firebase', + category: 'firebase', + duration: 30, + totalSteps: 11, + updatedAt: 'Jan 09, 2021', + featured: false, + progress: { + currentStep: 4, + completed: 3, + }, + }, + { + id: '55eb415f-3f4e-4853-a22b-f0ae91331169', + title: 'Keep Sensitive Data Safe and Private', + slug: 'keep-sensitive-data-safe-and-private', + description: 'Learn how to keep your important data safe and private', + category: 'android', + duration: 45, + totalSteps: 11, + updatedAt: 'Jan 14, 2021', + featured: false, + progress: { + currentStep: 6, + completed: 0, + }, + }, + { + id: 'fad2ab23-1011-4028-9a54-e52179ac4a50', + title: "Manage Your Pivotal Cloud Foundry App's Using Apigee Edge", + slug: 'manage-your-pivotal-cloud-foundry-apps-using-apigee-Edge', + description: 'Introductory course for Pivotal Cloud Foundry App', + category: 'cloud', + duration: 90, + totalSteps: 11, + updatedAt: 'Jun 24, 2021', + featured: false, + progress: { + currentStep: 6, + completed: 0, + }, + }, + { + id: 'c4bc107b-edc4-47a7-a7a8-4fb09732e794', + title: 'Build a PWA Using Workbox', + slug: 'build-a-pwa-using-workbox', + description: 'Step by step guide for building a PWA using Workbox', + category: 'web', + duration: 120, + totalSteps: 11, + updatedAt: 'Nov 19, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: '1449f945-d032-460d-98e3-406565a22293', + title: 'Cloud Functions for Firebase', + slug: 'cloud-functions-for-firebase', + description: 'Beginners guide of Firebase Cloud Functions', + category: 'firebase', + duration: 45, + totalSteps: 11, + updatedAt: 'Jul 11, 2021', + featured: false, + progress: { + currentStep: 3, + completed: 1, + }, + }, + { + id: 'f05e08ab-f3e3-4597-a032-6a4b69816f24', + title: 'Building a gRPC Service with Java', + slug: 'building-a-grpc-service-with-java', + description: 'Learn more about building a gRPC Service with Java', + category: 'cloud', + duration: 30, + totalSteps: 11, + updatedAt: 'Mar 13, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 1, + }, + }, + { + id: '181728f4-87c8-45c5-b9cc-92265bcd2f4d', + title: 'Looking at Campaign Finance with BigQuery', + slug: 'looking-at-campaign-finance-with-bigquery', + description: 'Dive deep into BigQuery: Campaign Finance', + category: 'cloud', + duration: 60, + totalSteps: 11, + updatedAt: 'Nov 01, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: 'fcbfedbf-6187-4b3b-89d3-1a7cb4e11616', + title: 'Personalize Your iOS App with Firebase User Management', + slug: 'personalize-your-ios-app-with-firebase-user-management', + description: + 'Dive deep into User Management on iOS apps using Firebase', + category: 'firebase', + duration: 90, + totalSteps: 11, + updatedAt: 'Aug 08, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: '5213f6a1-1dd7-4b1d-b6e9-ffb7af534f28', + title: 'Customize Network Topology with Subnetworks', + slug: 'customize-network-topology-with-subnetworks', + description: 'Dive deep into Network Topology with Subnetworks', + category: 'web', + duration: 45, + totalSteps: 11, + updatedAt: 'May 12, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: '02992ac9-d1a3-4167-b70e-8a1d5b5ba253', + title: 'Building Beautiful UIs with Flutter', + slug: 'building-beautiful-uis-with-flutter', + description: + "Dive deep into Flutter's hidden secrets for creating beautiful UIs", + category: 'web', + duration: 90, + totalSteps: 11, + updatedAt: 'Sep 18, 2021', + featured: false, + progress: { + currentStep: 8, + completed: 2, + }, + }, + { + id: '2139512f-41fb-4a4a-841a-0b4ac034f9b4', + title: 'Firebase Android', + slug: 'firebase-android', + description: 'Beginners guide of Firebase for Android', + category: 'android', + duration: 45, + totalSteps: 11, + updatedAt: 'Apr 24, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: '65e0a0e0-d8c0-4117-a3cb-eb74f8e28809', + title: 'Simulating a Thread Network Using OpenThread', + slug: 'simulating-a-thread-network-using-openthread', + description: + 'Introductory course for OpenThread and Simulating a Thread Network', + category: 'web', + duration: 45, + totalSteps: 11, + updatedAt: 'Jun 05, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: 'c202ebc9-9be3-433a-9d38-7003b3ed7b7a', + title: 'Your First Progressive Web App', + slug: 'your-first-progressive-web-app', + description: 'Step by step guide for creating a PWA from scratch', + category: 'web', + duration: 30, + totalSteps: 11, + updatedAt: 'Oct 14, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: '980ae7da-9f77-4e30-aa98-1b1ea594e775', + title: 'Launch Cloud Datalab', + slug: 'launch-cloud-datalab', + description: 'From start to finish: Launch Cloud Datalab', + category: 'cloud', + duration: 60, + totalSteps: 11, + updatedAt: 'Dec 16, 2021', + featured: false, + progress: { + currentStep: 0, + completed: 0, + }, + }, + { + id: 'c9748ea9-4117-492c-bdb2-55085b515978', + title: 'Cloud Firestore', + slug: 'cloud-firestore', + description: 'Step by step guide for setting up Cloud Firestore', + category: 'firebase', + duration: 90, + totalSteps: 11, + updatedAt: 'Apr 04, 2021', + featured: false, + progress: { + currentStep: 2, + completed: 0, + }, + }, +]; +export const demoCourseContent = ` +

+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus aperiam lab et fugiat id magnam minus nemo quam + voluptatem. Culpa deleniti explica nisi quod soluta. +

+

+ Alias animi labque, deserunt distinctio eum excepturi fuga iure labore magni molestias mollitia natus, officia pofro + quis sunt temporibus veritatis voluptatem, voluptatum. Aut blanditiis esse et illum maxim, obcaecati possimus + voluptate! Accusamus adipisci amet aperiam, assumenda consequuntur fugiat inventore iusto magnam molestias + natus necessitatibus, nulla pariatur. +

+

+ Amet distinctio enim itaque minima minus nesciunt recusandae soluta voluptatibus: +

+
+

+ Ad aliquid amet asperiores lab distinctio doloremque eaque, exercitationem explicabo, minus mollitia + natus necessitatibus odio omnis pofro rem. +

+
+

+ Alias architecto asperiores, dignissimos illum ipsam ipsum itaque, natus necessitatibus officiis, perferendis quae + sed ullam veniam vitae voluptas! Magni, nisi, quis! A accusamus animi commodi, consectetur distinctio + eaque, eos excepturi illum laboriosam maiores nam natus nulla officiis perspiciatis rem reprehenderit sed + tenetur veritatis. +

+

+ Consectetur dicta enim error eveniet expedita, facere in itaque labore natus quasi? Ad consectetur + eligendi facilis magni quae quis, quo temporibus voluptas voluptate voluptatem! +

+

+ Adipisci alias animi debitis eos et impedit maiores, modi nam nobis officia optio perspiciatis, rerum. + Accusantium esse nostrum odit quis quo: +

+
h1 a {{'{'}}
+    display: block;
+    width: 300px;
+    height: 80px;
+{{'}'}}
+

+ Accusantium aut autem, lab deleniti eaque fugiat fugit id ipsa iste molestiae, + necessitatibus nemo quasi + . +

+
+

+ Accusantium aspernatur autem enim +

+

+ Blanditiis, fugit voluptate! Assumenda blanditiis consectetur, labque cupiditate ducimus eaque earum, fugiat illum + ipsa, necessitatibus omnis quaerat reiciendis totam. Architecto, facere illum molestiae nihil nulla + quibusdam quidem vel! Atque blanditiis deserunt. +

+

+ Debitis deserunt doloremque labore laboriosam magni minus odit: +

+
    +
  1. Asperiores dicta esse maiores nobis officiis.
  2. +
  3. Accusamus aliquid debitis dolore illo ipsam molettiae possimus.
  4. +
  5. Magnam mollitia pariatur perspiciatis quasi quidem tenetur voluptatem! Adipisci aspernatur assumenda dicta.
  6. +
+

+ Animi fugit incidunt iure magni maiores molestias. +

+

+ Consequatur iusto soluta +

+

+ Aliquid asperiores corporis — deserunt dolorum ducimus eius eligendi explicabo quaerat suscipit voluptas. +

+

+ Deserunt dolor eos et illum laborum magni molestiae mollitia: +

+
+

Autem beatae consectetur consequatur, facere, facilis fugiat id illo, impedit numquam optio quis sunt ducimus illo.

+
+

+ Adipisci consequuntur doloribus facere in ipsam maxime molestias pofro quam: +

+
+ +
+ Accusamus blanditiis labque delectus esse et eum excepturi, impedit ipsam iste maiores minima mollitia, nihil obcaecati + placeat quaerat qui quidem sint unde! +
+
+

+ A beatae lab deleniti explicabo id inventore magni nisi omnis placeat praesentium quibusdam: +

+
    +
  • Dolorem eaque laboriosam omnis praesentium.
  • +
  • Atque debitis delectus distinctio doloremque.
  • +
  • Fuga illo impedit minima mollitia neque obcaecati.
  • +
+

+ Consequ eius eum excepturi explicabo. +

+

+ Adipisicing elit atque impedit? +

+

+ Atque distinctio doloremque ea qui quo, repellendus. +

+

+ Delectus deserunt explicabo facilis numquam quasi! Laboriosam, magni, quisquam. Aut, blanditiis commodi distinctio, facere fuga + hic itaque iure labore laborum maxime nemo neque provident quos recusandae sequi veritatis illum inventore iure qui rerum sapiente. +

+

+ Accusamus iusto sint aperiam consectetur … +

+

+ Aliquid assumenda ipsa nam odit pofro quaerat, quasi recusandae sint! Aut, esse explicabo facilis fugit illum iure magni + necessitatibus odio quas. +

+
    +
  • +

    Dolore natus placeat rem atque dignissimos laboriosam.

    +

    + Amet repudiandae voluptates architecto dignissimos repellendus voluptas dignissimos eveniet itaque maiores natus. +

    +

    + Accusamus aliquam debitis delectus dolorem ducimus enim eos, exercitationem fugiat id iusto nostrum quae quos + recusandae reiciendis rerum sequi temporibus veniam vero? Accusantium culpa, cupiditate ducimus eveniet id maiores modi + mollitia nisi aliquid dolorum ducimus et illo in. +

    +
  • +
  • +

    Ab amet deleniti dolor, et hic optio placeat.

    +

    + Accusantium ad alias beatae, consequatur consequuntur eos error eveniet expedita fuga laborum libero maxime nulla pofro + praesentium rem rerum saepe soluta ullam vero, voluptas? Architecto at debitis, doloribus harum iure libero natus odio + optio soluta veritatis voluptate. +

    +
  • +
  • +

    At aut consectetur nam necessitatibus neque nesciunt.

    +

    + Aut dignissimos labore nobis nostrum optio! Dolor id minima velit voluptatibus. Aut consequuntur eum exercitationem + fuga, harum id impedit molestiae natus neque numquam perspiciatis quam rem voluptatum. +

    +
  • +
+

+ Animi aperiam autem labque dolore enim ex expedita harum hic id impedit ipsa laborum modi mollitia non perspiciatis quae ratione. +

+

+ Alias eos excepturi facilis fugit. +

+

+ Alias asperiores, aspernatur corporis + delectus + est + facilis + inventore dolore + ipsa nobis nostrum officia quia, veritatis vero! At dolore est nesciunt numquam quam. Ab animi architecto aut, dignissimos + eos est eum explicabo. +

+

+ Adipisci autem consequuntur labque cupiditate dolor ducimus fuga neque nesciunt: +

+
module.exports = {{'{'}}
+    purge: [],
+    theme: {{'{'}}
+        extend: {{'{}'}},
+    },
+    variants: {{'{}'}},
+    plugins: [],
+{{'}'}}
+

+ Aliquid aspernatur eius fugit hic iusto. +

+

+ Dolorum ducimus expedita? +

+

+ Culpa debitis explicabo maxime minus quaerat reprehenderit temporibus! Asperiores, cupiditate ducimus esse est expedita fuga hic + ipsam necessitatibus placeat possimus? Amet animi aut consequuntur earum eveniet. +

+
    +
  1. + Aspernatur at beatae corporis debitis. +
      +
    • + Aperiam assumenda commodi lab dicta eius, “fugit ipsam“ itaque iure molestiae nihil numquam, officia omnis quia + repellendus sapiente sed. +
    • +
    • + Nulla odio quod saepe accusantium, adipisci autem blanditiis lab doloribus. +
    • +
    • + Explicabo facilis iusto molestiae nisi nostrum obcaecati officia. +
    • +
    +
  2. +
  3. + Nobis odio officiis optio quae quis quisquam quos rem. +
      +
    • Modi pariatur quod totam. Deserunt doloribus eveniet, expedita.
    • +
    • Ad beatae dicta et fugit libero optio quaerat rem repellendus./
    • +
    • Architecto atque consequuntur corporis id iste magni.
    • +
    +
  4. +
  5. + Deserunt non placeat unde veniam veritatis? Odio quod. +
      +
    • Inventore iure magni quod repellendus tempora. Magnam neque, quia. Adipisci amet.
    • +
    • Consectetur adipisicing elit.
    • +
    • labque eum expedita illo inventore iusto laboriosam nesciunt non, odio provident.
    • +
    +
  6. +
+

+ A aliquam architecto consequatur labque dicta doloremque <li> doloribus, ducimus earum, est <p> + eveniet explicabo fuga fugit ipsum minima minus molestias nihil nisi non qui sunt vel voluptatibus? A dolorum illum nihil quidem. +

+
    +
  • +

    + Laboriosam nesciunt obcaecati optio qui. +

    +

    + Doloremque magni molestias reprehenderit. +

    +
      +
    • Accusamus aperiam blanditiis <p> commodi
    • +
    • Dolorum ea explicabo fugiat in ipsum
    • +
    +
  • +
  • +

    + Commodi dolor dolorem dolores eum expedita libero. +

    +

    + Accusamus alias consectetur dolores et, excepturi fuga iusto possimus. +

    +
      +
    • +

      + Accusantium ad alias atque aut autem consequuntur deserunt dignissimos eaque iure <p> maxime. +

      +

      + Dolorum in nisi numquam omnis quam sapiente sit vero. +

      +
    • +
    • +

      + Adipisci lab in nisi odit soluta sunt vitae commodi excepturi. +

      +
    • +
    +
  • +
  • +

    + Assumenda deserunt distinctio dolor iste mollitia nihil non? +

    +
  • +
+

+ Consectetur adipisicing elit dicta dolor iste. +

+

+ Consectetur ea natus officia omnis reprehenderit. +

+

+ Distinctio impedit quaerat sed! Accusamus + aliquam aspernatur enim expedita explicabo + . Libero molestiae + odio quasi unde ut? Ab exercitationem id numquam odio quisquam! +

+

+ Explicabo facilis nemo quidem natus tempore: +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
WrestlerOriginFinisher
Bret “The Hitman” HartCalgary, ABSharpshooter
Stone Cold Steve AustinAustin, TXStone Cold Stunner
Randy SavageSarasota, FLElbow Drop
VaderBoulder, COVader Bomb
Razor RamonChuluota, FLRazor’s Edge
+

+ A aliquid autem lab doloremque, ea earum eum fuga fugit illo ipsa minus natus nisi <span> obcaecati pariatur + perferendis pofro suscipit tempore. +

+

+ Ad alias atque culpa illum earum optio +

+

+ Architecto consequatur eveniet illo in iure laborum minus omnis quibusdam sequi temporibus? Ab aliquid “atque dolores molestiae + nemo perferendis” reprehenderit saepe. +

+

+ Accusantium aliquid eligendi est fuga natus, quos vel? Adipisci aperiam asperiores aspernatur consectetur cupiditate + @distinctio/doloribus + et exercitationem expedita, facere facilis illum, impedit inventore + ipsa iure iusto magnam, magni minus nesciunt non officia possimus quod reiciendis. +

+

+ Cupiditate explicabo hic maiores +

+

+ Aliquam amet consequuntur distinctio ea est excepturi facere illum maiores nisi nobis non odit officiis + quisquam, similique tempora temporibus, tenetur ullam voluptates adipisci aperiam deleniti doloremque + ducimus eos. +

+

+ Ducimus qui quo tempora. lab enim explicabo hic inventore qui soluta voluptates voluptatum? Asperiores consectetur + delectus dolorem fugiat ipsa pariatur, quas quos repellendus repudiandae sunt aut blanditiis. +

+

+ Asperiores aspernatur autem error praesentium quidem. +

+

+ Ad blanditiis commodi, doloribus id iste repudiandae vero vitae. +

+

+ Atque consectetur lab debitis enim est et, facere fugit impedit, possimus quaerat quibusdam. +

+

+ Dolorem nihil placeat quibusdam veniam? Amet architecto at consequatur eligendi eveniet excepturi hic illo impedit in iste magni maxime + modi nisi nulla odio placeat quidem, quos rem repellat similique suscipit voluptate voluptates nobis omnis quo repellendus. +

+

+ Assumenda, eum, minima! Autem consectetur fugiat iste sit! Nobis omnis quo repellendus. +

+`; +export const demoCourseSteps = [ + { + order: 0, + title: 'Introduction', + subtitle: 'Introducing the library and how it works', + content: `

Introduction

${demoCourseContent}`, + }, + { + order: 1, + title: 'Get the sample code', + subtitle: 'Where to find the sample code and how to access it', + content: `

Get the sample code

${demoCourseContent}`, + }, + { + order: 2, + title: 'Create a Firebase project and Set up your app', + subtitle: + 'How to create a basic Firebase project and how to run it locally', + content: `

Create a Firebase project and Set up your app

${demoCourseContent}`, + }, + { + order: 3, + title: 'Install the Firebase Command Line Interface', + subtitle: 'Setting up the Firebase CLI to access command line tools', + content: `

Install the Firebase Command Line Interface

${demoCourseContent}`, + }, + { + order: 4, + title: 'Deploy and run the web app', + subtitle: 'How to build, push and run the project remotely', + content: `

Deploy and run the web app

${demoCourseContent}`, + }, + { + order: 5, + title: 'The Functions Directory', + subtitle: 'Introducing the Functions and Functions Directory', + content: `

The Functions Directory

${demoCourseContent}`, + }, + { + order: 6, + title: 'Import the Cloud Functions and Firebase Admin modules', + subtitle: + 'Create your first Function and run it to administer your app', + content: `

Import the Cloud Functions and Firebase Admin modules

${demoCourseContent}`, + }, + { + order: 7, + title: 'Welcome New Users', + subtitle: 'How to create a welcome message for the new users', + content: `

Welcome New Users

${demoCourseContent}`, + }, + { + order: 8, + title: 'Images moderation', + subtitle: 'How to moderate images; crop, resize, optimize', + content: `

Images moderation

${demoCourseContent}`, + }, + { + order: 9, + title: 'New Message Notifications', + subtitle: 'How to create and push a notification to a user', + content: `

New Message Notifications

${demoCourseContent}`, + }, + { + order: 10, + title: 'Congratulations!', + subtitle: 'Nice work, you have created your first application', + content: `

Congratulations!

${demoCourseContent}`, + }, +]; diff --git a/src/app/mock-api/apps/chat/api.ts b/src/app/mock-api/apps/chat/api.ts new file mode 100644 index 0000000..1f18102 --- /dev/null +++ b/src/app/mock-api/apps/chat/api.ts @@ -0,0 +1,160 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { + chats as chatsData, + contacts as contactsData, + messages as messagesData, + profile as profileData, +} from 'app/mock-api/apps/chat/data'; +import { assign, cloneDeep, omit } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class ChatMockApi { + private _chats: any[] = chatsData; + private _contacts: any[] = contactsData; + private _messages: any[] = messagesData; + private _profile: any = profileData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + + // Modify the chats array to attach certain data to it + this._chats = this._chats.map((chat) => ({ + ...chat, + // Get the actual contact object from the id and attach it to the chat + contact: this._contacts.find( + (contact) => contact.id === chat.contactId + ), + // Since we use same set of messages on all chats, we assign them here. + messages: this._messages.map((message) => ({ + ...message, + chatId: chat.id, + contactId: + message.contactId === 'me' + ? this._profile.id + : chat.contactId, + isMine: message.contactId === 'me', + })), + })); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Chats - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/chat/chats').reply(() => { + // Clone the chats + const chats = cloneDeep(this._chats); + + // Return the response + return [200, chats]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Chat - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/chat/chat') + .reply(({ request }) => { + // Get the chat id + const id = request.params.get('id'); + + // Clone the chats + const chats = cloneDeep(this._chats); + + // Find the chat we need + const chat = chats.find((item) => item.id === id); + + // Return the response + return [200, chat]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Chat - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/chat/chat') + .reply(({ request }) => { + // Get the id and chat + const id = request.body.id; + const chat = cloneDeep(request.body.chat); + + // Prepare the updated chat + let updatedChat = null; + + // Find the chat and update it + this._chats.forEach((item, index, chats) => { + if (item.id === id) { + // Update the chat + chats[index] = assign({}, chats[index], chat); + + // Store the updated chat + updatedChat = chats[index]; + } + }); + + // Return the response + return [200, updatedChat]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contacts - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/chat/contacts').reply(() => { + // Clone the contacts + let contacts = cloneDeep(this._contacts); + + // Sort the contacts by the name field by default + contacts.sort((a, b) => a.name.localeCompare(b.name)); + + // Omit details and attachments from contacts + contacts = contacts.map((contact) => + omit(contact, ['details', 'attachments']) + ); + + // Return the response + return [200, contacts]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contact Details - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/chat/contact') + .reply(({ request }) => { + // Get the contact id + const id = request.params.get('id'); + + // Clone the contacts + const contacts = cloneDeep(this._contacts); + + // Find the contact + const contact = contacts.find((item) => item.id === id); + + // Return the response + return [200, contact]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Profile - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/chat/profile').reply(() => { + // Clone the profile + const profile = cloneDeep(this._profile); + + // Return the response + return [200, profile]; + }); + } +} diff --git a/src/app/mock-api/apps/chat/data.ts b/src/app/mock-api/apps/chat/data.ts new file mode 100644 index 0000000..896c81d --- /dev/null +++ b/src/app/mock-api/apps/chat/data.ts @@ -0,0 +1,266 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +/** + * Attachments are common and will be filled from here + * to keep the demo data maintainable. + */ +const _attachments = { + media: [ + 'images/cards/01-320x200.jpg', + 'images/cards/02-320x200.jpg', + 'images/cards/03-320x200.jpg', + 'images/cards/04-320x200.jpg', + 'images/cards/05-320x200.jpg', + 'images/cards/06-320x200.jpg', + 'images/cards/07-320x200.jpg', + 'images/cards/08-320x200.jpg', + ], + docs: [], + links: [], +}; + +/** + * If a message belongs to our user, it's marked by setting it as + * 'me'. If it belongs to the user we are chatting with, then it + * left empty. We will be using this same conversation for each chat + * to keep things more maintainable for the demo. + */ +export const messages = [ + { + id: 'e6b2b82f-b199-4a60-9696-5f3e40d2715d', + contactId: 'me', + value: 'Hi!', + createdAt: now.minus({ week: 1 }).set({ hour: 18, minute: 56 }).toISO(), + }, + { + id: 'eb82cf4b-fa93-4bf4-a88a-99e987ddb7ea', + contactId: '', + value: 'Hey, dude!', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 4 }).toISO(), + }, + { + id: '3cf9b2a6-ae54-47db-97b2-ee139a8f84e5', + contactId: '', + value: 'Long time no see.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 4 }).toISO(), + }, + { + id: '2ab91b0f-fafb-45f3-88df-7efaff29134b', + contactId: 'me', + value: 'Yeah, man... Things were quite busy for me and my family.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 6 }).toISO(), + }, + { + id: '10e81481-378f-49ac-b06b-7c59dcc639ae', + contactId: '', + value: "What's up? Anything I can help with?", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 6 }).toISO(), + }, + { + id: '3b334e72-6605-4ebd-a4f6-3850067048de', + contactId: 'me', + value: "We've been on the move, changed 3 places over 4 months.", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 7 }).toISO(), + }, + { + id: '25998113-3a96-4dd0-a7b9-4d2bb58db3f3', + contactId: '', + value: "Wow! That's crazy! 🤯 What happened?", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 7 }).toISO(), + }, + { + id: '30adb3da-0e4f-487e-aec2-6d9f31e097f6', + contactId: 'me', + value: 'You know I got a job in that big software company. First move was because of that.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(), + }, + { + id: 'c0d6fd6e-d294-4845-8751-e84b8f2c4d3b', + contactId: 'me', + value: 'Then they decided to re-locate me after a month.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(), + }, + { + id: '8d3c442b-62fa-496f-bffa-210ff5c1866b', + contactId: 'me', + value: 'It was a pain since we just settled in, house, kids’ school, etc.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 8 }).toISO(), + }, + { + id: '3cf26ef0-e81f-4698-ac39-487454413332', + contactId: 'me', + value: 'So we moved again.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 9 }).toISO(), + }, + { + id: '415151b9-9ee9-40a4-a4ad-2d88146bc71b', + contactId: '', + value: "It's crazy!", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 9 }).toISO(), + }, + { + id: 'd6f29648-c85c-4dfb-a6ff-6b7ebc40c993', + contactId: 'me', + value: 'Then the virus happened, and we went remote after moving again.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 10 }).toISO(), + }, + { + id: '5329c20d-6754-47ec-af8c-660c72be3528', + contactId: 'me', + value: "So we moved back to the first location, the third time!", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 10 }).toISO(), + }, + { + id: '26f2ccbf-aef7-4b49-88df-f6b59381110a', + contactId: '', + value: "Ohh dude, that's tough in such a short period.", + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(), + }, + { + id: 'ea7662d5-7b72-4c19-ad6c-f80320541001', + contactId: '', + value: '😕', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(), + }, + { + id: '3a2d3a0e-839b-46e7-86ae-ca0826ecda7c', + contactId: 'me', + value: 'Thanks! It was great catching up.', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 11 }).toISO(), + }, + { + id: '562e3524-15b7-464a-bbf6-9b2582e5e0ee', + contactId: '', + value: 'Yeah! Let’s grab a coffee next week, remotely!', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(), + }, + { + id: '9269c775-bad5-46e1-b33b-2de8704ec1d6', + contactId: 'me', + value: 'Sure! See you next week!', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(), + }, + { + id: '779a27f2-bece-41c6-b9ca-c422570aee68', + contactId: '', + value: 'See you!', + createdAt: now.minus({ week: 1 }).set({ hour: 19, minute: 12 }).toISO(), + }, + { + id: 'bab8ca0e-b8e5-4375-807b-1c91fca25a5d', + contactId: 'me', + value: 'Hey! Available now? Let’s grab that coffee, remotely! :)', + createdAt: now.set({ hour: 12, minute: 45 }).toISO(), + }, + { + id: '8445a84d-599d-4e2d-a31c-5f4f29ad2b4c', + contactId: '', + value: 'Hi!', + createdAt: now.set({ hour: 12, minute: 56 }).toISO(), + }, + { + id: '9f506742-50da-4350-af9d-61e53392fa08', + contactId: '', + value: "Sure! I'll call you in 5, okay?", + createdAt: now.set({ hour: 12, minute: 56 }).toISO(), + }, + { + id: 'ca8523d8-faed-45f7-af09-f6bd5c3f3875', + contactId: 'me', + value: 'Awesome! Call me in 5 minutes.', + createdAt: now.set({ hour: 12, minute: 58 }).toISO(), + }, + { + id: '39944b00-1ffe-4ffb-8ca6-13c292812e06', + contactId: '', + value: '👍🏻', + createdAt: now.set({ hour: 13, minute: 0 }).toISO(), + }, +]; +export const chats = [ + { + id: 'ff6bc7f1-449a-4419-af62-b89ce6cae0aa', + contactId: '9d3f0e7f-dcbd-4e56-a5e8-87b8154e9edf', + unreadCount: 2, + muted: false, + lastMessage: 'See you tomorrow!', + lastMessageAt: '26/04/2021', + }, + { + id: '4459a3f0-b65e-4df2-8c37-6ec72fcc4b31', + contactId: '16b9e696-ea95-4dd8-86c4-3caf705a1dc6', + unreadCount: 0, + muted: false, + lastMessage: 'See you tomorrow!', + lastMessageAt: '26/04/2021', + } +]; +export const contacts = [ + { + id: '16b9e696-ea95-4dd8-86c4-3caf705a1dc6', + avatar: 'images/avatars/male-12.jpg', + name: 'Sali', + about: "Hi there! I'm using AngorChat.", + details: { + emails: [ + { + email: 'nunezfaulkner@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'xk', + phoneNumber: '909 552 3327', + label: 'Mobile', + }, + ], + title: 'Hotel Manager', + company: 'Buzzopia', + birthday: '1982-01-23T12:00:00.000Z', + address: '614 Herkimer Court, Darrtown, Nebraska, PO9308', + }, + attachments: _attachments, + }, + { + id: '9d3f0e7f-dcbd-4e56-a5e8-87b8154e9edf', + avatar: 'images/avatars/male-02.jpg', + name: 'John', + about: "Hi there! I'm using AngorChat.", + details: { + emails: [ + { + email: 'bernardlangley@mail.com', + label: 'Personal', + }, + { + email: 'langley.bernard@boilcat.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'md', + phoneNumber: '893 548 2862', + label: 'Mobile', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Boilcat', + birthday: '1988-05-26T12:00:00.000Z', + address: '943 Adler Place, Hamilton, South Dakota, PO5592', + }, + attachments: _attachments, + } +]; +export const profile: any = { + id: 'cfaad35d-07a3-4447-a6c3-d8c3d54fd5df', + name: 'Username', + email: 'username@angor.io', + avatar: 'images/avatars/username.jpg', + about: "Hi there! I'm using AngorChat.", +}; diff --git a/src/app/mock-api/apps/contacts/api.ts b/src/app/mock-api/apps/contacts/api.ts new file mode 100644 index 0000000..67ae4d6 --- /dev/null +++ b/src/app/mock-api/apps/contacts/api.ts @@ -0,0 +1,329 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api'; +import { + contacts as contactsData, + countries as countriesData, + tags as tagsData, +} from 'app/mock-api/apps/contacts/data'; +import { assign, cloneDeep } from 'lodash-es'; +import { from, map } from 'rxjs'; + +@Injectable({ providedIn: 'root' }) +export class ContactsMockApi { + private _contacts: any[] = contactsData; + private _countries: any[] = countriesData; + private _tags: any[] = tagsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Contacts - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/contacts/all').reply(() => { + // Clone the contacts + const contacts = cloneDeep(this._contacts); + + // Sort the contacts by the name field by default + contacts.sort((a, b) => a.name.localeCompare(b.name)); + + // Return the response + return [200, contacts]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contacts Search - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/contacts/search') + .reply(({ request }) => { + // Get the search query + const query = request.params.get('query'); + + // Clone the contacts + let contacts = cloneDeep(this._contacts); + + // If the query exists... + if (query) { + // Filter the contacts + contacts = contacts.filter( + (contact) => + contact.name && + contact.name + .toLowerCase() + .includes(query.toLowerCase()) + ); + } + + // Sort the contacts by the name field by default + contacts.sort((a, b) => a.name.localeCompare(b.name)); + + // Return the response + return [200, contacts]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contact - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/contacts/contact') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the contacts + const contacts = cloneDeep(this._contacts); + + // Find the contact + const contact = contacts.find((item) => item.id === id); + + // Return the response + return [200, contact]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contact - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/contacts/contact') + .reply(() => { + // Generate a new contact + const newContact = { + id: AngorMockApiUtils.guid(), + avatar: null, + name: 'New Contact', + emails: [], + phoneNumbers: [], + job: { + title: '', + company: '', + }, + birthday: null, + address: null, + notes: null, + tags: [], + }; + + // Unshift the new contact + this._contacts.unshift(newContact); + + // Return the response + return [200, newContact]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contact - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/contacts/contact') + .reply(({ request }) => { + // Get the id and contact + const id = request.body.id; + const contact = cloneDeep(request.body.contact); + + // Prepare the updated contact + let updatedContact = null; + + // Find the contact and update it + this._contacts.forEach((item, index, contacts) => { + if (item.id === id) { + // Update the contact + contacts[index] = assign({}, contacts[index], contact); + + // Store the updated contact + updatedContact = contacts[index]; + } + }); + + // Return the response + return [200, updatedContact]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Contact - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/contacts/contact') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the contact and delete it + this._contacts.forEach((item, index) => { + if (item.id === id) { + this._contacts.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Countries - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/contacts/countries') + .reply(() => [200, cloneDeep(this._countries)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/contacts/tags') + .reply(() => [200, cloneDeep(this._tags)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/contacts/tag') + .reply(({ request }) => { + // Get the tag + const newTag = cloneDeep(request.body.tag); + + // Generate a new GUID + newTag.id = AngorMockApiUtils.guid(); + + // Unshift the new tag + this._tags.unshift(newTag); + + // Return the response + return [200, newTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/contacts/tag') + .reply(({ request }) => { + // Get the id and tag + const id = request.body.id; + const tag = cloneDeep(request.body.tag); + + // Prepare the updated tag + let updatedTag = null; + + // Find the tag and update it + this._tags.forEach((item, index, tags) => { + if (item.id === id) { + // Update the tag + tags[index] = assign({}, tags[index], tag); + + // Store the updated tag + updatedTag = tags[index]; + } + }); + + // Return the response + return [200, updatedTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tag - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/contacts/tag') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the tag and delete it + this._tags.forEach((item, index) => { + if (item.id === id) { + this._tags.splice(index, 1); + } + }); + + // Get the contacts that have the tag + const contactsWithTag = this._contacts.filter( + (contact) => contact.tags.indexOf(id) > -1 + ); + + // Iterate through them and delete the tag + contactsWithTag.forEach((contact) => { + contact.tags.splice(contact.tags.indexOf(id), 1); + }); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Avatar - POST + // ----------------------------------------------------------------------------------------------------- + + /** + * Read the given file as mock-api url + * + * @param file + */ + const readAsDataURL = (file: File): Promise => + // Return a new promise + new Promise((resolve, reject) => { + // Create a new reader + const reader = new FileReader(); + + // Resolve the promise on success + reader.onload = (): void => { + resolve(reader.result); + }; + + // Reject the promise on error + reader.onerror = (e): void => { + reject(e); + }; + + // Read the file as the + reader.readAsDataURL(file); + }); + this._angorMockApiService + .onPost('api/apps/contacts/avatar') + .reply(({ request }) => { + // Get the id and avatar + const id = request.body.id; + const avatar = request.body.avatar; + + // Prepare the updated contact + let updatedContact: any = null; + + // In a real world application, this would return the path + // of the saved image file (from host, S3 bucket, etc.) but, + // for the sake of the demo, we encode the image to base64 + // and return it as the new path of the uploaded image since + // the src attribute of the img tag works with both image urls + // and encoded images. + return from(readAsDataURL(avatar)).pipe( + map((path) => { + // Find the contact and update it + this._contacts.forEach((item, index, contacts) => { + if (item.id === id) { + // Update the avatar + contacts[index].avatar = path; + + // Store the updated contact + updatedContact = contacts[index]; + } + }); + + // Return the response + return [200, updatedContact]; + }) + ); + }); + } +} diff --git a/src/app/mock-api/apps/contacts/data.ts b/src/app/mock-api/apps/contacts/data.ts new file mode 100644 index 0000000..22fe874 --- /dev/null +++ b/src/app/mock-api/apps/contacts/data.ts @@ -0,0 +1,4273 @@ +/* eslint-disable */ +export const contacts = [ + { + id: 'cd5fa417-b667-482d-b208-798d9da3213c', + avatar: 'images/avatars/male-01.jpg', + background: 'images/cards/14-640x480.jpg', + name: 'Dejesus Michael', + emails: [ + { + email: 'dejesusmichael@mail.org', + label: 'Personal', + }, + { + email: 'michael.dejesus@vitricomp.io', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'bs', + phoneNumber: '984 531 2468', + label: 'Mobile', + }, + { + country: 'bs', + phoneNumber: '806 470 2693', + label: 'Work', + }, + ], + title: 'Track Service Worker', + company: 'Vitricomp', + birthday: '1975-01-10T12:00:00.000Z', + address: '279 Independence Avenue, Calvary, Guam, PO4127', + notes: '

Do incididunt cillum duis eu pariatur enim proident minim officia amet proident consequat consequat qui consequat magna magna occaecat aliquip culpa pariatur velit nisi nostrud irure eu ullamco exercitation sint.

Cillum deserunt laborum laborum quis nisi enim et aliquip labore excepteur in excepteur labore amet in ipsum ipsum nostrud deserunt lorem nisi voluptate dolor minim enim ut eu cupidatat enim.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'beec5287-ed50-4504-858a-5dc3f8ce6935', + avatar: null, + background: null, + name: 'Dena Molina', + emails: [ + { + email: 'denamolina@mail.us', + label: 'Personal', + }, + { + email: 'molina.dena@envire.tv', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'io', + phoneNumber: '934 537 3180', + label: 'Mobile', + }, + ], + title: 'Weather Analyst', + company: 'Envire', + birthday: '1994-12-05T12:00:00.000Z', + address: '856 Woodside Avenue, Alfarata, Iowa, PO4992', + notes: '

Consequat duis ullamco sint elit pariatur esse dolore nostrud consequat lorem duis sunt veniam ipsum exercitation eiusmod consequat nisi quis voluptate quis officia irure fugiat ex duis eu amet ex.

Irure est nisi dolor culpa sunt nulla irure lorem adipisicing non do consequat deserunt et ea eu non reprehenderit fugiat ex elit nulla sunt quis voluptate enim nulla aliquip veniam.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '9d3f0e7f-dcbd-4e56-a5e8-87b8154e9edf', + avatar: 'images/avatars/male-02.jpg', + background: 'images/cards/15-640x480.jpg', + name: 'Bernard Langley', + emails: [ + { + email: 'bernardlangley@mail.com', + label: 'Personal', + }, + { + email: 'langley.bernard@boilcat.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'md', + phoneNumber: '893 548 2862', + label: 'Mobile', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Boilcat', + birthday: '1988-05-26T12:00:00.000Z', + address: '943 Adler Place, Hamilton, South Dakota, PO5592', + notes: '

Est amet in adipisicing ex excepteur ullamco est lorem adipisicing veniam reprehenderit elit commodo cillum commodo eu officia fugiat id reprehenderit sunt mollit eiusmod dolor fugiat ad do esse aliquip.

Mollit amet adipisicing enim est est commodo sint et eu nulla in laboris ipsum aliqua elit aliqua adipisicing ea nulla nulla consectetur velit laborum labore ullamco eu sit consectetur velit.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '42a5da95-5e6d-42fd-a09d-de755d123a47', + avatar: 'images/avatars/male-03.jpg', + background: 'images/cards/16-640x480.jpg', + name: 'Mclaughlin Steele', + emails: [ + { + email: 'mclaughlinsteele@mail.me', + label: 'Personal', + }, + { + email: 'steele.mclaughlin@accel.info', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'va', + phoneNumber: '830 484 3813', + label: 'Mobile', + }, + { + country: 'va', + phoneNumber: '999 475 2789', + label: 'Work', + }, + { + country: 'va', + phoneNumber: '933 406 3598', + label: 'Home', + }, + ], + company: 'Accel', + birthday: '1968-08-13T12:00:00.000Z', + address: '334 Sandford Street, Savage, Virgin Islands, PO1858', + notes: '

Consequat eu aliquip dolor non consequat laborum ad non labore cillum consectetur quis dolore do ea nulla incididunt proident ea eiusmod in do qui eiusmod et irure dolor ea adipisicing.

Reprehenderit occaecat nostrud ad aliquip commodo amet velit id ut minim dolor mollit mollit in eiusmod voluptate lorem nisi labore culpa elit proident laborum ipsum occaecat esse sint nostrud esse.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'a7806ced-03f1-4197-8b30-00bdd463366b', + avatar: 'images/avatars/male-04.jpg', + background: 'images/cards/17-640x480.jpg', + name: 'Marsh Cochran', + emails: [ + { + email: 'marshcochran@mail.biz', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'tz', + phoneNumber: '864 401 3980', + label: 'Mobile', + }, + { + country: 'tz', + phoneNumber: '956 546 2589', + label: 'Work', + }, + ], + title: 'Fundraising Director', + company: 'Xsports', + birthday: '1983-12-22T12:00:00.000Z', + address: '487 Hamilton Walk, Bergoo, American Samoa, PO5616', + notes: '

Id eiusmod deserunt amet lorem commodo consequat nostrud magna aliquip ex et pariatur labore non elit ad ad nulla culpa reprehenderit enim magna aliqua enim pariatur occaecat sint do lorem.

Adipisicing ut est nulla nisi cupidatat consequat aliqua et esse in voluptate amet eiusmod ut esse ea do irure commodo aute culpa amet consequat id adipisicing et incididunt ut duis.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: 'f4ad15d9-5a24-463a-88ea-6189d6bb3a53', + avatar: 'images/avatars/male-05.jpg', + background: 'images/cards/18-640x480.jpg', + name: 'Parrish Austin', + emails: [ + { + email: 'parrishaustin@mail.co.uk', + label: 'Personal', + }, + { + email: 'austin.parrish@insource.net', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'lv', + phoneNumber: '834 426 3574', + label: 'Mobile', + }, + { + country: 'lv', + phoneNumber: '816 573 3694', + label: 'Work', + }, + { + country: 'lv', + phoneNumber: '967 515 2009', + label: 'Home', + }, + ], + title: 'Motor Winder', + company: 'Insource', + birthday: '1963-08-24T12:00:00.000Z', + address: '610 Harbor Lane, Cascades, Minnesota, PO8639', + notes: '

Cillum enim eiusmod dolor aliqua ipsum exercitation sint aliqua lorem dolore id velit sint velit labore cupidatat minim cupidatat elit est magna eu proident eiusmod non pariatur est esse pariatur.

Sint do enim officia velit pariatur excepteur commodo adipisicing labore elit velit velit id exercitation excepteur veniam reprehenderit sint nulla duis ad incididunt cillum in in labore laboris magna esse.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '780d0111-5e5c-4694-8d1d-0ea421971fbf', + avatar: 'images/avatars/female-02.jpg', + background: 'images/cards/19-640x480.jpg', + name: 'Laverne Dodson', + emails: [ + { + email: 'lavernedodson@mail.ca', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'ar', + phoneNumber: '964 417 2318', + label: 'Mobile', + }, + { + country: 'ar', + phoneNumber: '830 410 2506', + label: 'Work', + }, + ], + title: 'Television News Producer', + company: 'Lovepad', + birthday: '1973-09-25T12:00:00.000Z', + address: '428 Newport Street, Neahkahnie, Arkansas, PO8324', + notes: '

Incididunt lorem proident est anim amet nulla do nulla ea anim ullamco ea amet voluptate laboris do elit elit consequat in esse in dolor enim irure ut irure ad commodo.

Aliqua dolore nulla sunt ad nostrud aute labore occaecat non amet nulla adipisicing sint eu lorem velit sint do sint adipisicing esse adipisicing anim culpa quis dolor non magna ea.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'bf172879-423a-4fd6-8df3-6d1938bbfe1f', + avatar: 'images/avatars/male-06.jpg', + background: 'images/cards/20-640x480.jpg', + name: 'Edwards Mckenzie', + emails: [ + { + email: 'edwardsmckenzie@mail.org', + label: 'Personal', + }, + { + email: 'mckenzie.edwards@bugsall.io', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'pe', + phoneNumber: '934 519 2903', + label: 'Mobile', + }, + { + country: 'pe', + phoneNumber: '989 489 3662', + label: 'Work', + }, + { + country: 'pe', + phoneNumber: '813 461 2790', + label: 'Home', + }, + ], + title: 'Legal Assistant', + company: 'Bugsall', + birthday: '1988-07-27T12:00:00.000Z', + address: '384 Polhemus Place, Dalton, Palau, PO6038', + notes: '

Eu veniam consectetur eiusmod anim sint anim consectetur do consectetur aliqua cillum proident fugiat do in aliqua ipsum id consequat commodo qui officia adipisicing ullamco occaecat laboris proident incididunt exercitation.

Velit ullamco magna aute proident irure ut magna ullamco labore dolor deserunt deserunt tempor fugiat ex ullamco do sunt veniam reprehenderit officia elit duis sint ut proident pariatur est reprehenderit.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '1eaa3213-ece2-4ba6-8e15-eb36ca388f50', + avatar: 'images/avatars/female-03.jpg', + background: 'images/cards/21-640x480.jpg', + name: 'Trudy Berg', + emails: [ + { + email: 'trudyberg@mail.us', + label: 'Personal', + }, + { + email: 'berg.trudy@satiance.tv', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'ls', + phoneNumber: '912 539 2770', + label: 'Mobile', + }, + ], + title: 'Meteorologist', + company: 'Satiance', + birthday: '1989-12-15T12:00:00.000Z', + address: '945 Jerome Avenue, Riceville, North Carolina, PO1625', + notes: '

Excepteur ullamco aute aliqua reprehenderit ullamco do anim ut ut veniam et ut et ut commodo aliqua consequat occaecat fugiat dolor labore proident ipsum ad culpa est cillum aliqua reprehenderit.

Amet aliqua sint laboris in aute nostrud voluptate tempor ea tempor laborum tempor culpa dolore aliqua nulla dolore ad enim id cupidatat nostrud nostrud amet non velit id fugiat lorem.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: 'abd9e78b-9e96-428f-b3ff-4d934c401bee', + avatar: 'images/avatars/female-04.jpg', + background: 'images/cards/22-640x480.jpg', + name: 'Elsie Melendez', + emails: [ + { + email: 'elsiemelendez@mail.com', + label: 'Personal', + }, + { + email: 'melendez.elsie@chillium.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'tg', + phoneNumber: '907 515 3007', + label: 'Mobile', + }, + { + country: 'tg', + phoneNumber: '967 534 2803', + label: 'Work', + }, + ], + title: 'Fundraising Director', + company: 'Chillium', + birthday: '1980-06-28T12:00:00.000Z', + address: '428 Varanda Place, Veyo, Oklahoma, PO6188', + notes: '

Laboris commodo consequat duis dolor ullamco nisi sunt ipsum nisi elit dolore aute sint tempor qui ad sit aliqua laboris consequat dolore aliqua est deserunt irure cillum tempor ut veniam.

Eiusmod nulla ex esse in deserunt consectetur non qui cillum reprehenderit magna sit ipsum lorem aute consequat sint magna id laboris velit adipisicing non ipsum ipsum sint velit ex non.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: 'efae92cc-3bd1-4c6a-a395-b6760c69bd55', + avatar: 'images/avatars/male-07.jpg', + background: 'images/cards/23-640x480.jpg', + name: 'Lamb Underwood', + emails: [ + { + email: 'lambunderwood@mail.me', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'pf', + phoneNumber: '855 517 2767', + label: 'Mobile', + }, + { + country: 'pf', + phoneNumber: '906 442 3593', + label: 'Work', + }, + { + country: 'pf', + phoneNumber: '905 402 2121', + label: 'Home', + }, + ], + title: 'Legal Assistant', + company: 'Exotechno', + birthday: '1990-07-26T12:00:00.000Z', + address: '609 Greenpoint Avenue, Beason, Vermont, PO5229', + notes: '

Exercitation tempor laboris dolor deserunt nulla et nisi ullamco minim duis sint nulla sint deserunt irure excepteur nostrud ipsum duis enim sit exercitation eiusmod tempor commodo excepteur mollit cupidatat fugiat.

Deserunt est dolore nulla laborum consequat veniam elit lorem do exercitation incididunt ea ad laboris lorem ipsum ex incididunt nostrud ipsum laborum et nostrud minim aute velit incididunt quis quis.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: 'bde636a7-c3d2-4bff-939a-aab11df1516b', + avatar: null, + background: null, + name: 'Tessa Valdez', + emails: [ + { + email: 'tessavaldez@mail.info', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'dz', + phoneNumber: '892 430 2631', + label: 'Mobile', + }, + { + country: 'dz', + phoneNumber: '997 525 2354', + label: 'Work', + }, + { + country: 'dz', + phoneNumber: '907 472 2857', + label: 'Home', + }, + ], + title: 'Banker Mason', + company: 'Securia', + birthday: '1994-01-10T12:00:00.000Z', + address: '183 Crosby Avenue, Blanco, Mississippi, PO3463', + notes: '

Mollit qui amet in esse ipsum nostrud cupidatat occaecat proident aliquip non mollit commodo ex labore enim culpa dolor aute occaecat cillum sit excepteur tempor culpa nostrud nulla qui commodo.

Labore nulla id excepteur non velit adipisicing tempor reprehenderit cillum sint do consectetur laboris ut proident pariatur quis aute ad dolor quis labore labore nostrud sunt elit proident enim aliqua.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '6519600a-5eaa-45f8-8bed-c46fddb3b26a', + avatar: 'images/avatars/male-08.jpg', + background: 'images/cards/24-640x480.jpg', + name: 'Mcleod Wagner', + emails: [ + { + email: 'mcleodwagner@mail.biz', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'at', + phoneNumber: '977 590 2773', + label: 'Mobile', + }, + { + country: 'at', + phoneNumber: '828 496 3813', + label: 'Work', + }, + { + country: 'at', + phoneNumber: '831 432 2512', + label: 'Home', + }, + ], + company: 'Inrt', + birthday: '1980-12-03T12:00:00.000Z', + address: '736 Glen Street, Kaka, West Virginia, PO9350', + notes: '

Laboris consequat est anim quis quis eiusmod ipsum non quis fugiat anim culpa non elit mollit pariatur veniam nisi irure velit dolore dolor proident nisi deserunt culpa nisi et laborum.

Eiusmod eu esse ipsum voluptate excepteur ipsum et proident cupidatat sint sunt aliquip lorem culpa esse et dolor fugiat sit est id consectetur sint et ea pariatur occaecat nulla irure.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '6d80a6f6-2884-4ac4-9c73-06b82c220017', + avatar: 'images/avatars/female-06.jpg', + background: 'images/cards/25-640x480.jpg', + name: 'Kristie Hall', + emails: [ + { + email: 'kristiehall@mail.co.uk', + label: 'Personal', + }, + { + email: 'hall.kristie@austech.net', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'tn', + phoneNumber: '841 530 3641', + label: 'Mobile', + }, + { + country: 'tn', + phoneNumber: '941 410 3743', + label: 'Work', + }, + { + country: 'tn', + phoneNumber: '938 599 3850', + label: 'Home', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Austech', + birthday: '1975-08-31T12:00:00.000Z', + address: '547 Revere Place, Hoehne, New Hampshire, PO2125', + notes: '

Duis incididunt minim nisi sit qui dolor aliquip quis ipsum id amet occaecat sit ullamco minim velit est eiusmod anim proident consectetur non reprehenderit ea reprehenderit dolore in nisi eiusmod.

Ut commodo aliqua non ut proident velit et commodo voluptate eu mollit dolor veniam ipsum velit aute esse est adipisicing id aliqua nostrud nostrud nisi enim officia eiusmod in enim.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '35190d23-036e-44ef-b545-cc744c626edd', + avatar: 'images/avatars/female-07.jpg', + background: 'images/cards/26-640x480.jpg', + name: 'Shannon Kennedy', + emails: [ + { + email: 'shannonkennedy@mail.ca', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'gb', + phoneNumber: '899 508 2992', + label: 'Mobile', + }, + { + country: 'gb', + phoneNumber: '834 499 3354', + label: 'Work', + }, + { + country: 'gb', + phoneNumber: '834 526 3388', + label: 'Home', + }, + ], + title: 'Gas Meter Mechanic', + company: 'Eventix', + birthday: '1994-09-07T12:00:00.000Z', + address: '480 Chase Court, Edinburg, Kansas, PO5357', + notes: '

Lorem ex amet anim anim qui consequat ullamco consectetur et voluptate in velit dolore culpa pariatur amet enim ut non magna duis qui excepteur esse ullamco velit fugiat aute dolor.

Reprehenderit ullamco veniam sit laborum nulla sunt excepteur eiusmod anim eu ullamco tempor est qui adipisicing sit fugiat voluptate minim non incididunt quis ipsum et exercitation officia laborum incididunt nostrud.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'b018c194-68ec-4915-ab56-e9f3bd2d98db', + avatar: 'images/avatars/female-08.jpg', + background: 'images/cards/27-640x480.jpg', + name: 'Martha Swanson', + emails: [ + { + email: 'marthaswanson@mail.org', + label: 'Personal', + }, + { + email: 'swanson.martha@sequitur.io', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'gb', + phoneNumber: '844 480 3309', + label: 'Mobile', + }, + { + country: 'gb', + phoneNumber: '981 591 3239', + label: 'Work', + }, + { + country: 'gb', + phoneNumber: '923 484 3147', + label: 'Home', + }, + ], + title: 'Short Story Writer', + company: 'Sequitur', + birthday: '1993-12-31T12:00:00.000Z', + address: '595 Howard Place, Convent, Rhode Island, PO6993', + notes: '

Lorem nostrud cillum non cillum nisi eu labore anim ipsum consequat consectetur sunt ipsum ipsum ad culpa laborum in ea exercitation quis voluptate velit id elit labore cillum cillum consectetur.

Ullamco ullamco nostrud aute pariatur nulla officia proident magna laborum dolor reprehenderit ullamco in reprehenderit veniam aliqua elit magna voluptate amet ut minim in labore irure culpa consequat sit pariatur.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'b7c355e9-e003-467e-82d2-4f6978c1a696', + avatar: 'images/avatars/female-09.jpg', + background: 'images/cards/28-640x480.jpg', + name: 'Jacklyn Morgan', + emails: [ + { + email: 'jacklynmorgan@mail.us', + label: 'Personal', + }, + { + email: 'morgan.jacklyn@shopabout.tv', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'so', + phoneNumber: '974 542 2061', + label: 'Mobile', + }, + ], + title: 'Animal Sitter', + company: 'Shopabout', + birthday: '1976-09-30T12:00:00.000Z', + address: '971 Conover Street, Statenville, Louisiana, PO6622', + notes: '

Pariatur fugiat labore aliquip aute in adipisicing veniam et consequat magna nulla laboris eiusmod eu esse cupidatat ipsum amet sint est anim lorem consequat eiusmod sit aliquip consequat nisi duis.

Est esse excepteur non amet reprehenderit cillum ullamco ex excepteur laboris excepteur dolor magna enim consequat lorem commodo ipsum elit ea veniam non quis id nisi esse tempor enim ut.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: 'cfa07b7c-93d1-42e7-9592-493d9efc78ae', + avatar: 'images/avatars/female-10.jpg', + background: 'images/cards/29-640x480.jpg', + name: 'Tonya Bowers', + emails: [ + { + email: 'tonyabowers@mail.com', + label: 'Personal', + }, + { + email: 'bowers.tonya@tourmania.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'tv', + phoneNumber: '922 585 2914', + label: 'Mobile', + }, + { + country: 'tv', + phoneNumber: '913 538 2961', + label: 'Work', + }, + ], + title: 'Track Service Worker', + company: 'Tourmania', + birthday: '1976-06-14T12:00:00.000Z', + address: '197 Marconi Place, Welda, Delaware, PO6061', + notes: '

Aliqua ea dolor est enim ipsum esse pariatur tempor nulla excepteur aliquip irure fugiat reprehenderit adipisicing ex tempor proident voluptate dolore ea dolore nostrud id incididunt culpa in do occaecat.

Aute fugiat magna velit enim in duis duis elit ipsum excepteur reprehenderit do ipsum qui cillum aliquip ut occaecat do ea et adipisicing cupidatat voluptate non elit ad aliqua ad.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: '00feeb63-c83a-4655-a37e-a07da10cfa1c', + avatar: 'images/avatars/female-11.jpg', + background: 'images/cards/30-640x480.jpg', + name: 'Latonya Cruz', + emails: [ + { + email: 'latonyacruz@mail.me', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'tm', + phoneNumber: '981 508 2080', + label: 'Mobile', + }, + { + country: 'tm', + phoneNumber: '817 425 2052', + label: 'Work', + }, + { + country: 'tm', + phoneNumber: '939 434 3805', + label: 'Home', + }, + ], + title: 'Motor Winder', + company: 'Zilch', + birthday: '1967-11-28T12:00:00.000Z', + address: '775 Dahill Road, Iberia, California, PO2169', + notes: '

Ut occaecat tempor deserunt proident enim ex ullamco ex aliquip mollit aute reprehenderit in occaecat anim aliquip ea laboris anim laboris do non aute aute ea laboris magna sunt sit.

Ullamco in in minim culpa eiusmod amet consequat consequat magna nisi cillum occaecat irure officia voluptate et eu duis officia nostrud culpa non eiusmod anim sint et anim enim voluptate.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '142abf21-e635-4a7d-9330-e57f66adcdbe', + avatar: 'images/avatars/female-12.jpg', + background: 'images/cards/31-640x480.jpg', + name: 'Evangelina Mcclain', + emails: [ + { + email: 'evangelinamcclain@mail.info', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'ck', + phoneNumber: '992 583 3187', + label: 'Mobile', + }, + { + country: 'ck', + phoneNumber: '881 472 3297', + label: 'Work', + }, + { + country: 'ck', + phoneNumber: '846 477 3596', + label: 'Home', + }, + ], + title: 'Congressional Representative', + company: 'Straloy', + birthday: '1976-02-15T12:00:00.000Z', + address: '305 Columbia Street, Dupuyer, Puerto Rico, PO8744', + notes: '

Proident nulla culpa magna nostrud do aliqua ullamco sit culpa ullamco eu amet culpa laborum enim fugiat non ad quis esse pariatur exercitation lorem incididunt exercitation aliquip labore minim adipisicing.

Sint ea voluptate tempor irure consequat aute laboris exercitation id minim voluptate aliquip tempor occaecat elit incididunt laboris enim labore sit aute sunt cillum ipsum ad laboris nostrud dolor excepteur.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: 'e4f255a3-b5dd-45a7-975f-c399604a399a', + avatar: 'images/avatars/male-09.jpg', + background: 'images/cards/32-640x480.jpg', + name: 'Herring Gonzales', + emails: [ + { + email: 'herringgonzales@mail.biz', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'ai', + phoneNumber: '995 411 2513', + label: 'Mobile', + }, + { + country: 'ai', + phoneNumber: '839 492 2760', + label: 'Work', + }, + ], + title: 'Gas Meter Mechanic', + company: 'Cubix', + birthday: '1995-02-16T12:00:00.000Z', + address: '195 Brooklyn Road, Jeff, Marshall Islands, PO2943', + notes: '

Ex nulla nisi do cillum consequat amet incididunt eu minim eu ut excepteur ad anim minim aliquip ullamco fugiat labore esse aliquip ea incididunt incididunt nisi officia consectetur dolore minim.

Et dolor consectetur anim deserunt laborum eu lorem et in nisi et officia nostrud fugiat deserunt aute irure ullamco officia fugiat voluptate exercitation ut deserunt officia nostrud tempor velit pariatur.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'ab4f712d-d712-41a8-b567-be4c66c349a3', + avatar: 'images/avatars/female-13.jpg', + background: 'images/cards/33-640x480.jpg', + name: 'Alyce Cash', + emails: [ + { + email: 'alycecash@mail.co.uk', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'ht', + phoneNumber: '969 499 3077', + label: 'Mobile', + }, + { + country: 'ht', + phoneNumber: '907 513 2784', + label: 'Work', + }, + ], + title: 'Weather Analyst', + company: 'Qnekt', + birthday: '1973-12-19T12:00:00.000Z', + address: '964 Henry Street, Eureka, Indiana, PO1035', + notes: '

Non proident pariatur nostrud dolor incididunt occaecat amet officia sunt magna anim dolor labore culpa ut laborum id incididunt officia amet mollit anim ea proident laboris non incididunt incididunt sint.

Nulla minim consectetur nostrud magna anim irure consectetur labore cupidatat laborum reprehenderit et et adipisicing in qui elit ipsum reprehenderit esse nisi non ipsum exercitation sunt eu elit velit fugiat.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '5d067800-c301-46c6-a7f7-28dc89d9a554', + avatar: null, + background: null, + name: 'Kristine Pacheco', + emails: [ + { + email: 'kristinepacheco@mail.net', + label: 'Personal', + }, + { + email: 'pacheco.kristine@vurbo.ca', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'mm', + phoneNumber: '977 516 2492', + label: 'Mobile', + }, + ], + title: 'Short Story Writer', + company: 'Vurbo', + birthday: '1985-10-22T12:00:00.000Z', + address: '622 Dodworth Street, Rose, Arizona, PO9530', + notes: '

Lorem laboris excepteur magna pariatur occaecat voluptate pariatur cillum exercitation anim enim elit laborum reprehenderit laboris ad velit ut ipsum irure id ullamco minim sint ipsum occaecat esse tempor ea.

Pariatur non labore cillum consectetur aute voluptate sint adipisicing nisi laborum culpa nisi elit et amet dolor incididunt velit ex laboris ea reprehenderit eiusmod qui esse veniam labore ea sit.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: 'c500255a-1173-47d0-a0e4-4944d48fc12a', + avatar: 'images/avatars/male-10.jpg', + background: 'images/cards/34-640x480.jpg', + name: 'English Haney', + emails: [ + { + email: 'englishhaney@mail.org', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'lb', + phoneNumber: '989 567 3834', + label: 'Mobile', + }, + ], + title: 'Meteorologist', + company: 'Photobin', + birthday: '1969-09-05T12:00:00.000Z', + address: '579 Pooles Lane, Belleview, Montana, PO4106', + notes: '

Incididunt labore sunt ullamco in deserunt dolore labore voluptate adipisicing eu id duis eiusmod elit ea ad cillum culpa excepteur labore fugiat excepteur ea culpa labore sit id dolor ullamco.

Eu eu ex dolore proident nostrud et minim lorem nulla lorem nulla duis velit voluptate nisi cillum anim minim amet dolore officia id cillum in cupidatat ipsum veniam velit dolor.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'b62359fd-f2a8-46e6-904e-31052d1cd675', + avatar: 'images/avatars/male-11.jpg', + background: 'images/cards/35-640x480.jpg', + name: 'Joseph Strickland', + emails: [ + { + email: 'josephstrickland@mail.io', + label: 'Personal', + }, + { + email: 'strickland.joseph@bytrex.us', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'jo', + phoneNumber: '990 450 2729', + label: 'Mobile', + }, + ], + title: 'Hotel Manager', + company: 'Bytrex', + birthday: '1991-09-08T12:00:00.000Z', + address: '844 Ellery Street, Hondah, Texas, PO1272', + notes: '

Excepteur consequat magna laborum dolore ut laborum ea excepteur ad officia mollit exercitation sunt tempor amet ex ipsum aliquip cillum mollit amet laborum voluptate ipsum sit esse duis eiusmod adipisicing.

Non tempor ad pariatur adipisicing excepteur est pariatur aute et velit lorem ut est eu voluptate pariatur ea consectetur excepteur sunt reprehenderit id irure aliqua tempor anim id voluptate culpa.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '16b9e696-ea95-4dd8-86c4-3caf705a1dc6', + avatar: 'images/avatars/male-12.jpg', + background: 'images/cards/36-640x480.jpg', + name: 'Nunez Faulkner', + emails: [ + { + email: 'nunezfaulkner@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'xk', + phoneNumber: '909 552 3327', + label: 'Mobile', + }, + ], + title: 'Hotel Manager', + company: 'Buzzopia', + birthday: '1982-01-23T12:00:00.000Z', + address: '614 Herkimer Court, Darrtown, Nebraska, PO9308', + notes: '

Culpa labore ullamco veniam est ullamco ipsum culpa excepteur esse esse aliqua nulla ullamco nulla amet consequat tempor aute exercitation do eu do ullamco elit excepteur est anim nisi excepteur.

Cillum eiusmod cupidatat officia ipsum ullamco adipisicing cillum adipisicing sint exercitation non enim consectetur est esse tempor fugiat sit eiusmod in exercitation enim quis duis dolor amet consequat pariatur dolor.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '19662ecf-0686-4aad-a46c-24b552eb2ff5', + avatar: 'images/avatars/female-15.jpg', + background: 'images/cards/14-640x480.jpg', + name: 'Juana Morrow', + emails: [ + { + email: 'juanamorrow@mail.com', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'ee', + phoneNumber: '868 438 3943', + label: 'Mobile', + }, + ], + title: 'Meteorologist', + company: 'Lyria', + birthday: '1992-03-29T12:00:00.000Z', + address: '663 Drew Street, Juntura, Georgia, PO9857', + notes: '

Mollit et amet qui incididunt officia anim est in consectetur qui anim qui labore ea mollit veniam adipisicing ex magna commodo mollit adipisicing sunt commodo laboris labore aliquip deserunt est.

Cupidatat ut cillum anim reprehenderit ea magna enim fugiat proident anim esse lorem lorem commodo cupidatat pariatur qui commodo nulla aliqua nisi labore in adipisicing minim excepteur do eu amet.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '26dfe954-8bf3-45ee-b285-1d0a88c8d3ea', + avatar: 'images/avatars/male-13.jpg', + background: 'images/cards/15-640x480.jpg', + name: 'Lara Gaines', + emails: [ + { + email: 'laragaines@mail.name', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'mr', + phoneNumber: '891 498 2043', + label: 'Mobile', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Acruex', + birthday: '1961-06-07T12:00:00.000Z', + address: '762 Troutman Street, Drummond, Oregon, PO6973', + notes: '

Laboris dolor incididunt eiusmod deserunt officia labore eu est nulla velit id ex veniam qui fugiat velit irure reprehenderit dolor proident aliquip culpa nisi magna occaecat do nostrud cillum lorem.

Sit consequat laboris culpa quis laborum lorem ullamco occaecat labore duis ea et consequat pariatur reprehenderit excepteur excepteur exercitation sunt enim amet adipisicing laborum incididunt dolor aliquip culpa ea laboris.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: 'd6462af2-c488-4de7-9b26-3845bd2983f9', + avatar: 'images/avatars/male-14.jpg', + background: 'images/cards/16-640x480.jpg', + name: 'Johnston Riddle', + emails: [ + { + email: 'johnstonriddle@mail.me', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bt', + phoneNumber: '979 541 2691', + label: 'Mobile', + }, + { + country: 'bt', + phoneNumber: '909 407 3887', + label: 'Work', + }, + { + country: 'bt', + phoneNumber: '864 557 3128', + label: 'Home', + }, + ], + title: 'Hotel Manager', + company: 'Xleen', + birthday: '1972-09-13T12:00:00.000Z', + address: + '674 Bryant Street, Grahamtown, Federated States Of Micronesia, PO2757', + notes: '

Velit consequat elit anim qui eu elit aliquip consectetur aliqua cupidatat lorem laboris dolor qui ad laborum adipisicing adipisicing consequat et nostrud ullamco consequat dolore deserunt irure do aliquip non.

Ipsum commodo voluptate qui ex ullamco amet do ex dolore quis cupidatat ut anim sunt dolore excepteur anim do dolor aliqua ex aute esse eiusmod sint laborum consequat laboris cillum.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'a1723c04-69fe-4573-a135-6645658afe76', + avatar: null, + background: null, + name: 'Vargas Gardner', + emails: [ + { + email: 'vargasgardner@mail.info', + label: 'Personal', + }, + { + email: 'gardner.vargas@cosmosis.biz', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'bi', + phoneNumber: '855 456 2754', + label: 'Mobile', + }, + ], + title: 'Bindery Machine Operator', + company: 'Cosmosis', + birthday: '1979-10-21T12:00:00.000Z', + address: '869 Seton Place, Chemung, Maine, PO8109', + notes: '

Amet non anim ex ullamco pariatur ullamco laboris eiusmod ut magna nisi amet incididunt sunt anim nisi qui ut ex sunt adipisicing consequat deserunt qui mollit duis anim quis veniam.

Magna ut id duis qui ea proident quis officia lorem commodo et et proident dolore qui quis incididunt nulla incididunt ut aliqua veniam est adipisicing adipisicing reprehenderit ad velit incididunt.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '823e6166-c0c8-4373-9270-8a0d17489a08', + avatar: 'images/avatars/male-16.jpg', + background: 'images/cards/17-640x480.jpg', + name: 'Mccall Day', + emails: [ + { + email: 'mccallday@mail.co.uk', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'se', + phoneNumber: '993 504 3286', + label: 'Mobile', + }, + { + country: 'se', + phoneNumber: '924 434 2238', + label: 'Work', + }, + { + country: 'se', + phoneNumber: '816 466 2634', + label: 'Home', + }, + ], + title: 'Historiographer', + company: 'Nipaz', + birthday: '1964-03-05T12:00:00.000Z', + address: '854 Hanover Place, Harleigh, New Jersey, PO9459', + notes: '

Ea occaecat nisi cillum officia in velit ipsum reprehenderit ex fugiat fugiat ad velit pariatur ullamco sint in elit quis aute id cupidatat nostrud quis culpa aliquip id officia excepteur.

Ea ut consequat sit ullamco do pariatur quis officia ad ipsum quis nisi in nulla incididunt esse pariatur amet qui ullamco consectetur dolor voluptate sit qui mollit reprehenderit reprehenderit amet.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '2c37ed00-427a-46d7-8f8f-d711c768d1ee', + avatar: 'images/avatars/male-17.jpg', + background: 'images/cards/18-640x480.jpg', + name: 'Silva Foster', + emails: [ + { + email: 'silvafoster@mail.net', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bn', + phoneNumber: '916 511 3837', + label: 'Mobile', + }, + { + country: 'bn', + phoneNumber: '949 564 3247', + label: 'Work', + }, + ], + title: 'Insurance Analyst', + company: 'Extrawear', + birthday: '1980-04-29T12:00:00.000Z', + address: '137 Bridge Street, Sisquoc, District Of Columbia, PO4105', + notes: '

Ipsum velit est do velit do deserunt cupidatat officia duis laborum veniam sunt in ex reprehenderit esse ex ad aute anim duis ut sunt reprehenderit occaecat ut nostrud eu minim.

Aliqua consequat adipisicing adipisicing aliquip voluptate fugiat eu amet nostrud id proident non nisi fugiat velit nostrud ea officia non laboris magna cillum exercitation culpa eiusmod mollit fugiat et lorem.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '944764c0-b261-4428-9188-bbd3022d66a8', + avatar: 'images/avatars/female-16.jpg', + background: 'images/cards/19-640x480.jpg', + name: 'Cathryn Snider', + emails: [ + { + email: 'cathrynsnider@mail.ca', + label: 'Personal', + }, + { + email: 'snider.cathryn@phormula.org', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'na', + phoneNumber: '896 471 3036', + label: 'Mobile', + }, + { + country: 'na', + phoneNumber: '851 491 3567', + label: 'Work', + }, + { + country: 'na', + phoneNumber: '805 487 2016', + label: 'Home', + }, + ], + title: 'Short Story Writer', + company: 'Phormula', + birthday: '1981-06-09T12:00:00.000Z', + address: '528 Glenmore Avenue, Elrama, Illinois, PO2952', + notes: '

Ea enim exercitation lorem excepteur officia nulla culpa culpa nisi veniam quis non duis exercitation labore commodo et occaecat reprehenderit ex velit exercitation commodo cupidatat amet veniam mollit magna consectetur.

Voluptate consectetur eu id eiusmod anim reprehenderit incididunt duis veniam tempor cillum ea esse tempor do laborum dolore sint ea duis incididunt in do aliqua voluptate incididunt officia excepteur do.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'f2b3c756-5ad2-4d4b-aee5-b32c91457128', + avatar: null, + background: null, + name: 'Mooney Cantrell', + emails: [ + { + email: 'mooneycantrell@mail.io', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bh', + phoneNumber: '915 577 3020', + label: 'Mobile', + }, + { + country: 'bh', + phoneNumber: '923 431 3594', + label: 'Work', + }, + ], + title: 'Fundraising Director', + company: 'Crustatia', + birthday: '1968-12-07T12:00:00.000Z', + address: '277 Coventry Road, Fairforest, Nevada, PO6031', + notes: '

Lorem mollit dolore nostrud sunt id anim veniam labore duis eiusmod duis fugiat aliqua occaecat do labore culpa consectetur consectetur sunt amet tempor incididunt tempor esse sunt id elit non.

Laborum mollit ullamco quis ad culpa nisi sit nisi veniam minim adipisicing sint eiusmod velit amet minim aliquip nulla eiusmod nulla laboris quis proident in adipisicing aute et ea anim.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: '54b1c201-4b2b-4be0-ad70-a6413e9628cd', + avatar: 'images/avatars/female-17.jpg', + background: 'images/cards/20-640x480.jpg', + name: 'Saundra Murphy', + emails: [ + { + email: 'saundramurphy@mail.us', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'mt', + phoneNumber: '902 529 2999', + label: 'Mobile', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Zilencio', + birthday: '1983-11-07T12:00:00.000Z', + address: '557 Monroe Street, Mayfair, Maryland, PO7200', + notes: '

Fugiat mollit sunt aliquip consectetur ipsum ut aliqua id ex laboris labore id elit nulla irure id aute pariatur do officia proident eiusmod proident reprehenderit dolor non dolor laborum nulla.

Pariatur reprehenderit incididunt voluptate enim aliqua laborum anim veniam pariatur irure exercitation non dolore velit et ex culpa lorem ipsum mollit eu sint duis aliquip elit amet consectetur velit minim.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'faf979c7-a13b-445a-b30a-08845f5fa90e', + avatar: 'images/avatars/female-18.jpg', + background: 'images/cards/21-640x480.jpg', + name: 'Enid Sparks', + emails: [ + { + email: 'enidsparks@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bh', + phoneNumber: '813 410 3258', + label: 'Mobile', + }, + { + country: 'bh', + phoneNumber: '877 501 2767', + label: 'Work', + }, + ], + title: 'Historiographer', + company: 'Skybold', + birthday: '1984-05-04T12:00:00.000Z', + address: '219 Village Court, Keyport, Alabama, PO7776', + notes: '

Velit enim anim est aliqua consequat exercitation velit quis magna est incididunt ipsum minim minim nulla adipisicing ad eiusmod id veniam eiusmod sit elit est pariatur velit ea laborum anim.

Ad lorem ea nisi irure id consequat ullamco nisi nostrud dolore officia ipsum veniam velit minim pariatur culpa culpa esse minim adipisicing sit labore commodo aute excepteur non do in.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', + avatar: null, + background: null, + name: 'Nadia Mcknight', + emails: [ + { + email: 'nadiamcknight@mail.com', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'tk', + phoneNumber: '943 511 2203', + label: 'Mobile', + }, + { + country: 'tk', + phoneNumber: '817 578 2993', + label: 'Work', + }, + ], + title: 'Legal Assistant', + company: 'Pearlesex', + birthday: '1973-10-06T12:00:00.000Z', + address: '448 Berriman Street, Reinerton, Washington, PO6704', + notes: '

Esse sint lorem exercitation velit tempor tempor voluptate nulla proident excepteur magna tempor consectetur aliquip qui nisi mollit cupidatat est adipisicing ipsum sint et excepteur sit labore velit dolore labore.

Duis nisi adipisicing lorem do excepteur magna consequat labore magna ut consectetur eu enim occaecat id nulla laboris minim officia est id nisi mollit ullamco irure ut dolore esse aliqua.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '77a4383b-b5a5-4943-bc46-04c3431d1566', + avatar: 'images/avatars/male-19.jpg', + background: 'images/cards/22-640x480.jpg', + name: 'Best Blackburn', + emails: [ + { + email: 'bestblackburn@mail.name', + label: 'Personal', + }, + { + email: 'blackburn.best@beadzza.me', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'gl', + phoneNumber: '814 498 3701', + label: 'Mobile', + }, + ], + title: 'Hotel Manager', + company: 'Beadzza', + birthday: '1987-06-07T12:00:00.000Z', + address: '578 Tampa Court, Wescosville, Ohio, PO4108', + notes: '

Lorem do deserunt nulla nostrud incididunt et laboris labore eu nisi ut ullamco veniam deserunt do non labore commodo amet aliquip exercitation ea occaecat amet non eiusmod ut minim fugiat.

Esse eu ex irure pariatur qui cillum labore nulla quis officia consequat commodo consequat fugiat culpa nostrud labore eu adipisicing magna irure aliquip est amet irure eiusmod esse reprehenderit mollit.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '8bb0f597-673a-47ca-8c77-2f83219cb9af', + avatar: null, + background: null, + name: 'Duncan Carver', + emails: [ + { + email: 'duncancarver@mail.info', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'jm', + phoneNumber: '968 547 2111', + label: 'Mobile', + }, + { + country: 'jm', + phoneNumber: '968 433 3120', + label: 'Work', + }, + { + country: 'jm', + phoneNumber: '905 425 2777', + label: 'Home', + }, + ], + title: 'Historiographer', + company: 'Hotcakes', + birthday: '1980-09-15T12:00:00.000Z', + address: '931 Bristol Street, Why, South Carolina, PO9700', + notes: '

Dolore laboris aute officia reprehenderit cupidatat aliquip duis labore aliquip officia est nostrud nisi voluptate eiusmod ad aute et ea cillum aliqua elit ipsum officia cillum laborum minim labore sit.

Exercitation labore ut pariatur occaecat ullamco non occaecat aliqua amet nostrud aliquip ipsum ad do ullamco enim laborum commodo minim elit ut quis laboris elit laborum proident sunt ullamco sit.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb', + avatar: 'images/avatars/male-01.jpg', + background: 'images/cards/23-640x480.jpg', + name: 'Martin Richards', + emails: [ + { + email: 'martinrichards@mail.biz', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'mg', + phoneNumber: '902 500 2668', + label: 'Mobile', + }, + { + country: 'mg', + phoneNumber: '947 559 2919', + label: 'Work', + }, + { + country: 'mg', + phoneNumber: '934 434 3768', + label: 'Home', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Overfork', + birthday: '1977-04-12T12:00:00.000Z', + address: '268 Hutchinson Court, Drytown, Florida, PO3041', + notes: '

Eu ipsum nisi eu lorem cupidatat mollit exercitation elit ea culpa enim qui culpa ad aliqua exercitation tempor nulla excepteur fugiat ipsum quis amet occaecat adipisicing ullamco duis dolore occaecat.

Non eu et elit ea labore lorem adipisicing voluptate incididunt ut officia aute minim incididunt lorem qui adipisicing mollit magna nisi consectetur cillum sit exercitation eiusmod qui eu nisi sunt.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '0a8bc517-631a-4a93-aacc-000fa2e8294c', + avatar: 'images/avatars/female-20.jpg', + background: 'images/cards/24-640x480.jpg', + name: 'Candice Munoz', + emails: [ + { + email: 'candicemunoz@mail.co.uk', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'fm', + phoneNumber: '838 562 2769', + label: 'Mobile', + }, + ], + title: 'Legal Assistant', + company: 'Eclipto', + birthday: '1976-09-09T12:00:00.000Z', + address: '946 Remsen Street, Caroline, New Mexico, PO3247', + notes: '

Amet dolore elit irure in commodo in et eu eu nulla labore elit sunt et nisi quis officia nostrud et mollit dolor aute fugiat sunt reprehenderit quis sint minim ipsum.

Laboris ut sunt nisi aute incididunt reprehenderit mollit culpa velit exercitation reprehenderit irure id sunt officia magna est ea labore consectetur incididunt cillum qui tempor ea ullamco quis pariatur aliquip.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'a4c9945a-757b-40b0-8942-d20e0543cabd', + avatar: 'images/avatars/female-01.jpg', + background: 'images/cards/25-640x480.jpg', + name: 'Vickie Mosley', + emails: [ + { + email: 'vickiemosley@mail.net', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'tr', + phoneNumber: '939 555 3054', + label: 'Mobile', + }, + { + country: 'tr', + phoneNumber: '852 486 2053', + label: 'Work', + }, + ], + title: 'Bindery Machine Operator', + company: 'Strozen', + birthday: '1989-06-21T12:00:00.000Z', + address: '397 Vandalia Avenue, Rockingham, Michigan, PO8089', + notes: '

Velit sunt sunt commodo ex amet laboris voluptate eu lorem aliqua minim occaecat cupidatat aliqua ipsum nisi velit id reprehenderit exercitation velit fugiat minim nisi deserunt voluptate anim cillum commodo.

Cillum velit nostrud cupidatat ex sit culpa deserunt cillum cupidatat cillum aute cupidatat exercitation ullamco sunt incididunt non magna sint lorem et incididunt laborum culpa qui sint sunt duis fugiat.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645', + avatar: 'images/avatars/female-02.jpg', + background: 'images/cards/26-640x480.jpg', + name: 'Tina Harris', + emails: [ + { + email: 'tinaharris@mail.ca', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'gp', + phoneNumber: '933 464 2431', + label: 'Mobile', + }, + { + country: 'gp', + phoneNumber: '894 535 3609', + label: 'Work', + }, + ], + title: 'Short Story Writer', + company: 'Gallaxia', + birthday: '1976-09-10T12:00:00.000Z', + address: '821 Beverly Road, Tyro, Colorado, PO4248', + notes: '

Incididunt non est consequat qui sit sunt aliquip sit quis minim laboris ullamco est culpa velit culpa cupidatat veniam incididunt non quis elit reprehenderit et officia cillum magna aliqua occaecat.

Cupidatat amet incididunt id pariatur minim veniam id dolor nisi labore cillum ea officia cupidatat do culpa aliqua consequat deserunt aliquip sit ea excepteur eiusmod labore tempor reprehenderit commodo exercitation.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d', + avatar: 'images/avatars/male-02.jpg', + background: 'images/cards/27-640x480.jpg', + name: 'Holt Manning', + emails: [ + { + email: 'holtmanning@mail.org', + label: 'Personal', + }, + { + email: 'manning.holt@idetica.io', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'nz', + phoneNumber: '822 531 2600', + label: 'Mobile', + }, + { + country: 'nz', + phoneNumber: '922 549 2094', + label: 'Work', + }, + ], + title: 'Fundraising Director', + company: 'Idetica', + birthday: '1973-11-08T12:00:00.000Z', + address: '364 Porter Avenue, Delshire, Missouri, PO8911', + notes: '

Velit fugiat minim sit nisi esse laboris ad velit proident non et cillum labore sint excepteur nisi eu amet voluptate duis duis id enim ea anim adipisicing consectetur id consectetur.

Ex eiusmod id magna in non lorem sunt sunt officia do adipisicing officia mollit occaecat sunt laborum aliquip adipisicing ullamco in sit proident et quis incididunt pariatur fugiat mollit anim.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37', + avatar: 'images/avatars/female-03.jpg', + background: 'images/cards/28-640x480.jpg', + name: 'Misty Ramsey', + emails: [ + { + email: 'mistyramsey@mail.us', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'kp', + phoneNumber: '990 457 2106', + label: 'Mobile', + }, + { + country: 'kp', + phoneNumber: '918 550 2946', + label: 'Work', + }, + ], + company: 'Grupoli', + birthday: '1969-08-10T12:00:00.000Z', + address: '101 Sackett Street, Naomi, Tennessee, PO6335', + notes: '

Ut cupidatat sint minim consectetur cupidatat aute ut anim consequat fugiat laboris quis sint sit nulla irure nulla officia eiusmod consequat ex quis ad ex ullamco et ut labore tempor.

Deserunt minim dolore voluptate aute aliqua est elit mollit ut ut consequat in esse est do ex officia nostrud aute id fugiat reprehenderit quis cillum fugiat id fugiat minim tempor.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: 'cdcc62e4-1520-4ccc-803d-52868c7e01ba', + avatar: 'images/avatars/female-04.jpg', + background: 'images/cards/29-640x480.jpg', + name: 'Dee Alvarado', + emails: [ + { + email: 'deealvarado@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'nu', + phoneNumber: '855 445 2483', + label: 'Mobile', + }, + { + country: 'nu', + phoneNumber: '858 415 2860', + label: 'Work', + }, + { + country: 'nu', + phoneNumber: '968 587 2752', + label: 'Home', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Tsunamia', + birthday: '1996-06-17T12:00:00.000Z', + address: '956 Pierrepont Street, Crumpler, Hawaii, PO3299', + notes: '

Esse excepteur ad aliquip amet elit reprehenderit ut nostrud magna ex esse dolore magna excepteur irure esse incididunt sunt enim laborum ex mollit magna elit quis ullamco aute minim veniam.

Duis id ullamco laboris elit ea ea dolore tempor est eu esse aliqua quis quis ut laborum mollit cillum proident deserunt fugiat ipsum elit exercitation quis mollit eiusmod officia non.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'e2946946-b4b5-4fd7-bab4-62c38cdff2f1', + avatar: 'images/avatars/female-05.jpg', + background: 'images/cards/30-640x480.jpg', + name: 'Samantha Jacobson', + emails: [ + { + email: 'samanthajacobson@mail.com', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'es', + phoneNumber: '879 591 3327', + label: 'Mobile', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Emoltra', + birthday: '1972-02-04T12:00:00.000Z', + address: '384 Love Lane, Dyckesville, New York, PO4115', + notes: '

Consectetur eu et ea anim magna occaecat anim labore velit nulla non magna laboris duis sit adipisicing commodo laboris consequat id quis aliqua est culpa quis in ex est culpa.

Sunt qui excepteur reprehenderit nostrud voluptate eu laborum laborum id esse occaecat irure esse elit magna tempor ad est elit non labore tempor laborum deserunt voluptate cupidatat excepteur sunt sint.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'fdc77706-6ba2-4397-b2f8-a9a0b6495153', + avatar: 'images/avatars/female-06.jpg', + background: 'images/cards/31-640x480.jpg', + name: 'Rhea Landry', + emails: [ + { + email: 'rhealandry@mail.name', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'jp', + phoneNumber: '906 579 3698', + label: 'Mobile', + }, + { + country: 'jp', + phoneNumber: '841 475 2681', + label: 'Work', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Comtent', + birthday: '1988-05-22T12:00:00.000Z', + address: '725 Arlington Avenue, Mathews, Wyoming, PO4562', + notes: '

Eiusmod ullamco laboris tempor reprehenderit culpa non sunt ea consequat velit id ipsum commodo eiusmod exercitation laboris aliqua magna reprehenderit culpa tempor mollit pariatur consectetur amet aliqua cillum voluptate exercitation.

Qui cillum consectetur qui proident adipisicing id qui esse aute velit excepteur pariatur ea excepteur sunt velit nostrud esse mollit sint ex irure sunt aliquip velit consequat minim do officia.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '12148fa2-e0a4-49fb-b3c5-daeecdb5180a', + avatar: 'images/avatars/female-07.jpg', + background: 'images/cards/32-640x480.jpg', + name: 'Olga Rhodes', + emails: [ + { + email: 'olgarhodes@mail.me', + label: 'Personal', + }, + { + email: 'rhodes.olga@moreganic.info', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'tl', + phoneNumber: '971 514 3366', + label: 'Mobile', + }, + { + country: 'tl', + phoneNumber: '807 480 2033', + label: 'Work', + }, + { + country: 'tl', + phoneNumber: '810 528 3783', + label: 'Home', + }, + ], + title: 'Pastry Baker', + company: 'Moreganic', + birthday: '1971-08-13T12:00:00.000Z', + address: '253 Beard Street, Staples, Massachusetts, PO8089', + notes: '

Proident est est et in commodo incididunt anim fugiat laboris pariatur eu enim dolor eiusmod dolor voluptate officia eiusmod excepteur culpa aute do do anim pariatur irure incididunt incididunt est.

Sint duis mollit dolor laborum ex non esse consequat anim et qui est nostrud incididunt fugiat anim veniam sunt cupidatat ut voluptate commodo non ex tempor ullamco magna culpa culpa.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '07dd64eb-8b8f-4765-a16c-8db083c45096', + avatar: 'images/avatars/female-08.jpg', + background: 'images/cards/33-640x480.jpg', + name: 'Lorraine Pennington', + emails: [ + { + email: 'lorrainepennington@mail.biz', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'fm', + phoneNumber: '932 404 3308', + label: 'Mobile', + }, + { + country: 'fm', + phoneNumber: '979 550 3200', + label: 'Work', + }, + { + country: 'fm', + phoneNumber: '868 557 3568', + label: 'Home', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Marvane', + birthday: '1967-06-10T12:00:00.000Z', + address: '962 Whitney Avenue, Sussex, North Dakota, PO5796', + notes: '

Nulla nisi officia quis aliquip voluptate mollit ut anim eu et quis tempor incididunt consectetur exercitation cupidatat in nisi exercitation est culpa nostrud sit elit sit sunt do ipsum eu.

Enim voluptate ad ullamco tempor voluptate culpa et ut ullamco eu consequat est esse excepteur est nostrud velit enim culpa dolore non quis occaecat eiusmod velit ex mollit tempor labore.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '81fdc48c-5572-4123-8a73-71b7892120de', + avatar: 'images/avatars/female-09.jpg', + background: 'images/cards/34-640x480.jpg', + name: 'Earlene Rosales', + emails: [ + { + email: 'earlenerosales@mail.co.uk', + label: 'Personal', + }, + { + email: 'rosales.earlene@softmicro.net', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'ki', + phoneNumber: '927 589 3619', + label: 'Mobile', + }, + ], + title: 'Historiographer', + company: 'Softmicro', + birthday: '1960-11-13T12:00:00.000Z', + address: '981 Kingston Avenue, Topaz, Connecticut, PO6866', + notes: '

Adipisicing fugiat magna eiusmod consectetur id commodo incididunt ullamco ut sint minim nulla in do aute in sit pariatur irure dolor magna pariatur ad officia excepteur duis ullamco dolor sunt.

Dolor laborum proident voluptate eu esse lorem adipisicing enim consectetur veniam nisi pariatur aliquip sit laborum sunt adipisicing anim labore eiusmod nostrud irure irure nisi ipsum dolor aliquip ex exercitation.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: 'f8bbf6be-d49a-41a3-bb80-3d51df84c12b', + avatar: 'images/avatars/female-10.jpg', + background: 'images/cards/35-640x480.jpg', + name: 'Marcia Hatfield', + emails: [ + { + email: 'marciahatfield@mail.ca', + label: 'Personal', + }, + { + email: 'hatfield.marcia@datagen.org', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'no', + phoneNumber: '883 432 3718', + label: 'Mobile', + }, + { + country: 'no', + phoneNumber: '934 516 2135', + label: 'Work', + }, + { + country: 'no', + phoneNumber: '923 596 3843', + label: 'Home', + }, + ], + title: 'Track Service Worker', + company: 'Datagen', + birthday: '1980-02-26T12:00:00.000Z', + address: '802 Preston Court, Waikele, Pennsylvania, PO7421', + notes: '

Aliqua sint aute in cillum deserunt enim fugiat tempor est pariatur irure commodo commodo deserunt eu nulla laboris enim occaecat incididunt voluptate enim est reprehenderit qui anim veniam sint adipisicing.

Commodo veniam occaecat ex et laborum minim fugiat sunt commodo velit dolor labore excepteur fugiat ipsum eiusmod in esse ex nulla deserunt minim consectetur in est sunt eu commodo fugiat.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: 'cd482941-3eaf-4560-ac37-56a9296025df', + avatar: 'images/avatars/female-11.jpg', + background: 'images/cards/36-640x480.jpg', + name: 'Liliana Ayala', + emails: [ + { + email: 'lilianaayala@mail.io', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bd', + phoneNumber: '936 590 2412', + label: 'Mobile', + }, + ], + title: 'Insurance Analyst', + company: 'Pharmex', + birthday: '1988-04-27T12:00:00.000Z', + address: '935 Guider Avenue, Kipp, Wisconsin, PO5282', + notes: '

Magna et culpa cillum sint labore consequat aute aliqua amet ea consequat ut ullamco nisi commodo lorem enim amet dolor sit nisi dolor do sit lorem cillum esse reprehenderit ut.

Quis veniam anim nulla adipisicing veniam fugiat elit duis pariatur anim irure adipisicing elit labore eu aute exercitation qui exercitation commodo exercitation ipsum tempor non et ex eu aute proident.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '22f18d47-ff8d-440e-888d-a1747c093052', + avatar: 'images/avatars/female-12.jpg', + background: 'images/cards/14-640x480.jpg', + name: 'Alice Harding', + emails: [ + { + email: 'aliceharding@mail.us', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'sx', + phoneNumber: '881 472 3113', + label: 'Mobile', + }, + { + country: 'sx', + phoneNumber: '974 548 3124', + label: 'Work', + }, + { + country: 'sx', + phoneNumber: '800 518 3615', + label: 'Home', + }, + ], + title: 'Track Service Worker', + company: 'Futurity', + birthday: '1985-09-17T12:00:00.000Z', + address: '387 Holt Court, Thomasville, Alaska, PO2867', + notes: '

Adipisicing exercitation dolor nisi ipsum nostrud anim dolore sint veniam consequat lorem sit ex commodo nostrud occaecat elit magna magna commodo incididunt laborum ad irure pariatur et sit ullamco adipisicing.

Ullamco in dolore amet est quis consectetur fugiat non nisi incididunt id laborum adipisicing dolor proident velit ut quis aliquip dolore id anim sit adipisicing nisi incididunt enim amet pariatur.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: 'a9a9f382-e4c3-42fb-9fe9-65aa534732b5', + avatar: 'images/avatars/female-13.jpg', + background: 'images/cards/15-640x480.jpg', + name: 'Francisca Perkins', + emails: [ + { + email: 'franciscaperkins@mail.tv', + label: 'Personal', + }, + { + email: 'perkins.francisca@overplex.com', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'au', + phoneNumber: '830 430 3437', + label: 'Mobile', + }, + { + country: 'au', + phoneNumber: '868 538 2886', + label: 'Work', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Overplex', + birthday: '1966-08-14T12:00:00.000Z', + address: '733 Delmonico Place, Belvoir, Virginia, PO7102', + notes: '

Voluptate nisi adipisicing ex magna mollit non cillum dolor in magna duis exercitation irure elit duis eiusmod deserunt lorem nulla sunt laboris quis voluptate ullamco labore adipisicing quis minim ipsum.

Id ut esse elit proident mollit nulla exercitation magna voluptate sit eiusmod labore velit commodo exercitation dolore anim est eiusmod occaecat et consequat eiusmod culpa ipsum deserunt lorem non incididunt.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: '0222b24b-c288-48d1-b356-0f087fa172f8', + avatar: null, + background: null, + name: 'Warren Gates', + emails: [ + { + email: 'warrengates@mail.name', + label: 'Personal', + }, + { + email: 'gates.warren@qualitex.me', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'gt', + phoneNumber: '847 513 2248', + label: 'Mobile', + }, + { + country: 'gt', + phoneNumber: '866 591 3665', + label: 'Work', + }, + { + country: 'gt', + phoneNumber: '877 539 3840', + label: 'Home', + }, + ], + title: 'Banker Mason', + company: 'Qualitex', + birthday: '1977-02-23T12:00:00.000Z', + address: '713 Fane Court, Lemoyne, Kentucky, PO3601', + notes: '

Sint tempor consectetur ullamco ullamco consequat exercitation ea occaecat eiusmod cupidatat anim pariatur nisi pariatur excepteur ut labore anim excepteur sit eu consequat do enim pariatur et dolore in irure.

Commodo ut non minim sunt nisi tempor culpa duis anim ipsum qui irure lorem est voluptate voluptate officia occaecat lorem labore elit officia laboris mollit et eiusmod esse laborum nisi.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '0630f1ca-cdb9-405d-b134-68f733334089', + avatar: 'images/avatars/female-14.jpg', + background: 'images/cards/16-640x480.jpg', + name: 'Maryann Mcintyre', + emails: [ + { + email: 'maryannmcintyre@mail.info', + label: 'Personal', + }, + { + email: 'mcintyre.maryann@aquafire.biz', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'bf', + phoneNumber: '861 419 2752', + label: 'Mobile', + }, + { + country: 'bf', + phoneNumber: '935 553 3031', + label: 'Work', + }, + ], + title: 'Fundraising Director', + company: 'Aquafire', + birthday: '1963-04-07T12:00:00.000Z', + address: '698 Brooklyn Avenue, Dixonville, Utah, PO2712', + notes: '

Pariatur velit ea ad quis elit pariatur consectetur eiusmod veniam non incididunt ex ex et nulla voluptate fugiat esse sit dolore voluptate in dolor nulla laborum irure consequat sit pariatur.

Dolore ex officia incididunt pariatur ea amet sunt enim aute labore cupidatat laboris eiusmod enim lorem labore nostrud ea consectetur et eu sunt exercitation dolore consequat fugiat anim in exercitation.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '999c24f3-7bb8-4a01-85ca-2fca7863c57e', + avatar: 'images/avatars/female-15.jpg', + background: 'images/cards/17-640x480.jpg', + name: 'Sharon Marshall', + emails: [ + { + email: 'sharonmarshall@mail.co.uk', + label: 'Personal', + }, + { + email: 'marshall.sharon@utara.net', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'fo', + phoneNumber: '947 441 2999', + label: 'Mobile', + }, + { + country: 'fo', + phoneNumber: '984 441 2615', + label: 'Work', + }, + { + country: 'fo', + phoneNumber: '824 541 2714', + label: 'Home', + }, + ], + title: 'Legal Assistant', + company: 'Utara', + birthday: '1960-01-26T12:00:00.000Z', + address: '923 Ivan Court, Hatteras, Idaho, PO7573', + notes: '

Est duis sint ullamco nulla do tempor do dolore laboris in sint ad duis est eu consequat nisi esse irure tempor sunt pariatur qui mollit ipsum quis esse ex ipsum.

Dolore anim irure quis ipsum adipisicing sint et incididunt aute nisi minim aliquip consectetur duis tempor laborum nostrud exercitation do mollit irure anim lorem non excepteur commodo laborum dolore dolor.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '7e8e1f1e-d19f-45c7-86bd-6fef599dae71', + avatar: 'images/avatars/female-16.jpg', + background: 'images/cards/18-640x480.jpg', + name: 'Margo Witt', + emails: [ + { + email: 'margowitt@mail.ca', + label: 'Personal', + }, + { + email: 'witt.margo@norsul.org', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'ao', + phoneNumber: '992 596 3391', + label: 'Mobile', + }, + { + country: 'ao', + phoneNumber: '950 489 2505', + label: 'Work', + }, + { + country: 'ao', + phoneNumber: '891 540 2231', + label: 'Home', + }, + ], + title: 'Television News Producer', + company: 'Norsul', + birthday: '1975-08-31T12:00:00.000Z', + address: '539 Rockaway Avenue, Whitmer, Guam, PO4871', + notes: '

Sunt quis officia elit laborum excepteur consequat amet cillum labore deserunt cillum cillum labore exercitation minim laboris anim incididunt voluptate minim duis enim eu duis veniam labore nisi culpa duis.

Pariatur irure sunt et commodo reprehenderit consectetur duis et ullamco fugiat occaecat culpa enim incididunt officia minim aliqua sit amet do dolore pariatur fugiat et adipisicing labore dolor id dolore.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: 'bedcb6a2-da83-4631-866a-77d10d239477', + avatar: 'images/avatars/male-04.jpg', + background: 'images/cards/19-640x480.jpg', + name: 'Alvarado Turner', + emails: [ + { + email: 'alvaradoturner@mail.io', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'lv', + phoneNumber: '961 537 3956', + label: 'Mobile', + }, + ], + title: 'Fundraising Director', + company: 'Geologix', + birthday: '1985-12-08T12:00:00.000Z', + address: '233 Willmohr Street, Cressey, Iowa, PO1962', + notes: '

In amet voluptate ad eiusmod cupidatat nulla sunt eu amet occaecat qui cillum occaecat tempor minim nostrud ullamco amet elit aliquip est nisi officia lorem occaecat ea lorem officia veniam.

Nulla tempor id excepteur irure do do veniam eiusmod esse ipsum sint dolore commodo enim officia nulla nulla proident in dolor et aliquip sit nulla sit proident duis aute deserunt.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '66f9de1b-f842-4d4c-bb59-f97e91db0462', + avatar: 'images/avatars/male-05.jpg', + background: 'images/cards/20-640x480.jpg', + name: 'Maldonado Rodriquez', + emails: [ + { + email: 'maldonadorodriquez@mail.us', + label: 'Personal', + }, + { + email: 'rodriquez.maldonado@zentility.tv', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'et', + phoneNumber: '811 502 3398', + label: 'Mobile', + }, + { + country: 'et', + phoneNumber: '877 402 2443', + label: 'Work', + }, + { + country: 'et', + phoneNumber: '949 536 3451', + label: 'Home', + }, + ], + title: 'Dental Laboratory Worker', + company: 'Zentility', + birthday: '1993-06-01T12:00:00.000Z', + address: '916 Cobek Court, Morningside, South Dakota, PO2019', + notes: '

Laboris consequat labore nisi aute voluptate minim amet nulla elit tempor dolor nulla do et consequat esse dolore fugiat laboris deserunt velit minim laboris voluptate enim ut non laboris nisi.

Magna pariatur voluptate veniam nostrud irure magna pariatur ut quis reprehenderit voluptate aute duis sunt laboris consequat lorem eu pariatur nulla incididunt quis lorem consectetur ex lorem commodo magna dolore.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '9cb0ea57-3461-4182-979b-593b0c1ec6c3', + avatar: 'images/avatars/male-06.jpg', + background: 'images/cards/21-640x480.jpg', + name: 'Tran Duke', + emails: [ + { + email: 'tranduke@mail.com', + label: 'Personal', + }, + { + email: 'duke.tran@splinx.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'si', + phoneNumber: '837 503 2254', + label: 'Mobile', + }, + { + country: 'si', + phoneNumber: '893 405 3190', + label: 'Work', + }, + { + country: 'si', + phoneNumber: '931 402 3874', + label: 'Home', + }, + ], + title: 'Legal Assistant', + company: 'Splinx', + birthday: '1976-04-27T12:00:00.000Z', + address: '405 Canarsie Road, Richville, Virgin Islands, PO2744', + notes: '

Occaecat do excepteur non ipsum labore consequat id eu sunt minim aliquip elit occaecat velit ut aute cupidatat irure ex eiusmod fugiat ea ea cupidatat nulla dolor labore consectetur amet.

Mollit enim dolore deserunt tempor aliqua velit nostrud nostrud id consectetur lorem in enim excepteur nisi laborum ex commodo sint ea et culpa lorem esse culpa ad officia do amet.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '2fb89a90-5622-4b5b-8df3-d49b85905392', + avatar: null, + background: null, + name: 'Estela Lyons', + emails: [ + { + email: 'estelalyons@mail.me', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'vg', + phoneNumber: '864 459 3205', + label: 'Mobile', + }, + { + country: 'vg', + phoneNumber: '886 524 2880', + label: 'Work', + }, + { + country: 'vg', + phoneNumber: '815 484 3420', + label: 'Home', + }, + ], + title: 'Animal Sitter', + company: 'Gonkle', + birthday: '1968-03-11T12:00:00.000Z', + address: '540 Metrotech Courtr, Garfield, American Samoa, PO2290', + notes: '

Ullamco dolore ipsum exercitation officia dolore sit consequat nisi consequat occaecat et ipsum veniam anim tempor pariatur sunt in adipisicing aliqua non dolor laborum veniam nisi dolore quis sunt incididunt.

Incididunt ullamco sunt magna reprehenderit velit dolor qui anim eiusmod nostrud commodo exercitation velit incididunt exercitation nulla ad aute eiusmod est amet exercitation est nostrud sit esse esse ad irure.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: '8141dd08-3a6e-4770-912c-59d0ed06dde6', + avatar: null, + background: null, + name: 'Madeleine Fletcher', + emails: [ + { + email: 'madeleinefletcher@mail.info', + label: 'Personal', + }, + { + email: 'fletcher.madeleine@genmom.biz', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'uy', + phoneNumber: '898 554 3354', + label: 'Mobile', + }, + ], + title: 'Fundraising Director', + company: 'Genmom', + birthday: '1970-07-15T12:00:00.000Z', + address: '825 Cherry Street, Foscoe, Minnesota, PO7290', + notes: '

Fugiat in exercitation nostrud labore labore irure ex magna ex aliquip veniam sit irure irure deserunt occaecat tempor cillum aliqua dolore ea tempor dolore laboris est amet quis consequat quis.

Esse officia velit consectetur ullamco ea pariatur mollit sit consectetur sint mollit commodo anim anim ea amet consectetur eiusmod aliqua excepteur elit laborum magna non fugiat nisi pariatur ut velit.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '7585015c-ada2-4f88-998d-9646865d1ad2', + avatar: 'images/avatars/male-07.jpg', + background: 'images/cards/22-640x480.jpg', + name: 'Meyer Roach', + emails: [ + { + email: 'meyerroach@mail.co.uk', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'uz', + phoneNumber: '891 543 2053', + label: 'Mobile', + }, + { + country: 'uz', + phoneNumber: '842 564 3671', + label: 'Work', + }, + { + country: 'uz', + phoneNumber: '992 491 3514', + label: 'Home', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Zentime', + birthday: '1968-10-16T12:00:00.000Z', + address: '315 Albemarle Road, Allison, Arkansas, PO6008', + notes: '

Eiusmod deserunt aliqua dolore ipsum cillum veniam minim dolore nulla aute aliqua voluptate labore sint cillum excepteur nulla nostrud do cupidatat eu adipisicing reprehenderit deserunt elit qui mollit adipisicing eu.

Proident commodo magna eu voluptate eiusmod aliqua laborum eu ea elit quis ullamco ullamco magna minim enim amet dolore sit lorem aliqua officia amet officia non magna enim cillum sit.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '32c73a6a-67f2-48a9-b2a1-b23da83187bb', + avatar: null, + background: null, + name: 'Bolton Obrien', + emails: [ + { + email: 'boltonobrien@mail.net', + label: 'Personal', + }, + { + email: 'obrien.bolton@enersol.ca', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'tn', + phoneNumber: '860 472 2458', + label: 'Mobile', + }, + { + country: 'tn', + phoneNumber: '887 499 3580', + label: 'Work', + }, + ], + title: 'Banker Mason', + company: 'Enersol', + birthday: '1968-09-08T12:00:00.000Z', + address: '818 Aviation Road, Geyserville, Palau, PO9655', + notes: '

Cupidatat lorem tempor commodo do eu ea dolor eiusmod do nisi occaecat fugiat labore non esse aliquip ullamco laboris adipisicing pariatur nostrud enim minim do fugiat culpa exercitation lorem duis.

Pariatur cupidatat tempor est et nostrud in amet aliquip sint nulla amet ea lorem irure sint sit ea aliquip voluptate id laboris fugiat cillum cillum dolore deserunt fugiat ad tempor.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '114642a2-ccb7-4cb1-ad2b-5e9b6a0c1d2e', + avatar: 'images/avatars/male-09.jpg', + background: 'images/cards/23-640x480.jpg', + name: 'Barber Johnson', + emails: [ + { + email: 'barberjohnson@mail.org', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'az', + phoneNumber: '928 567 2521', + label: 'Mobile', + }, + { + country: 'az', + phoneNumber: '898 515 2048', + label: 'Work', + }, + { + country: 'az', + phoneNumber: '935 495 3348', + label: 'Home', + }, + ], + title: 'Talent Manager', + company: 'Zounds', + birthday: '1967-03-02T12:00:00.000Z', + address: '386 Vernon Avenue, Dragoon, North Carolina, PO4559', + notes: '

Esse amet ex duis esse aliqua non tempor ullamco dolore et aliquip nisi pariatur qui laborum id consequat tempor sint eiusmod exercitation velit aliquip occaecat tempor nisi aute magna sint.

Deserunt veniam voluptate dolore eiusmod eu consequat dolor sit pariatur laboris anim excepteur consequat nulla officia exercitation magna sint ea excepteur qui eu officia pariatur culpa sint elit nulla officia.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '310ece7d-dbb0-45d6-9e69-14c24e50fe3d', + avatar: 'images/avatars/male-10.jpg', + background: 'images/cards/24-640x480.jpg', + name: 'Cervantes Kramer', + emails: [ + { + email: 'cervanteskramer@mail.io', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'vg', + phoneNumber: '998 498 2507', + label: 'Mobile', + }, + { + country: 'vg', + phoneNumber: '856 477 3445', + label: 'Work', + }, + ], + title: 'Motor Winder', + company: 'Xeronk', + birthday: '1992-09-04T12:00:00.000Z', + address: '238 Rochester Avenue, Lydia, Oklahoma, PO3914', + notes: '

Excepteur do ullamco voluptate deserunt tempor ullamco enim non incididunt adipisicing sunt sint sit qui occaecat occaecat id laboris et duis amet reprehenderit cupidatat aliquip dolore ea eu ea nulla.

Cillum nulla deserunt laboris eu sint dolor non laboris cupidatat aute nisi amet mollit ipsum cillum excepteur consequat tempor exercitation consequat nostrud ipsum qui excepteur eiusmod nostrud laboris pariatur sint.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: 'dcc673f6-de59-4715-94ed-8f64663d449b', + avatar: 'images/avatars/female-19.jpg', + background: 'images/cards/25-640x480.jpg', + name: 'Megan Suarez', + emails: [ + { + email: 'megansuarez@mail.us', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bb', + phoneNumber: '875 422 2053', + label: 'Mobile', + }, + { + country: 'bb', + phoneNumber: '861 487 2597', + label: 'Work', + }, + { + country: 'bb', + phoneNumber: '873 414 3953', + label: 'Home', + }, + ], + title: 'Bindery Machine Operator', + company: 'Cemention', + birthday: '1984-09-08T12:00:00.000Z', + address: '112 Tillary Street, Camptown, Vermont, PO8827', + notes: '

Pariatur tempor laborum deserunt commodo eiusmod adipisicing amet anim irure fugiat laboris velit do velit elit aute deserunt officia fugiat nulla ullamco est elit veniam officia sit veniam velit commodo.

Laboris duis eu adipisicing esse fugiat voluptate enim sint in voluptate lorem laboris eiusmod commodo nostrud dolor qui incididunt non fugiat culpa aliquip minim voluptate lorem sint sunt velit eiusmod.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '3e4ca731-d39b-4ad9-b6e0-f84e67f4b74a', + avatar: 'images/avatars/female-20.jpg', + background: 'images/cards/26-640x480.jpg', + name: 'Ofelia Ratliff', + emails: [ + { + email: 'ofeliaratliff@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'vu', + phoneNumber: '978 546 3699', + label: 'Mobile', + }, + { + country: 'vu', + phoneNumber: '892 551 2229', + label: 'Work', + }, + { + country: 'vu', + phoneNumber: '949 495 3479', + label: 'Home', + }, + ], + company: 'Buzzmaker', + birthday: '1988-11-11T12:00:00.000Z', + address: '951 Hampton Avenue, Bartonsville, Mississippi, PO4232', + notes: '

Ad lorem id irure aute ipsum ex occaecat commodo dolore eu dolor exercitation anim quis officia deserunt lorem sunt officia eu sit aliquip laborum id duis aliqua quis aute magna.

Do do lorem est amet aliqua ex excepteur nisi cupidatat esse consequat ipsum in ad eiusmod proident cupidatat dolore anim ut pariatur sint do elit incididunt officia adipisicing amet eu.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, + { + id: '2012d4a5-19e4-444d-aaff-1d8b1d853650', + avatar: 'images/avatars/female-01.jpg', + background: 'images/cards/27-640x480.jpg', + name: 'Laurel Parker', + emails: [ + { + email: 'laurelparker@mail.com', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'lu', + phoneNumber: '805 502 3677', + label: 'Mobile', + }, + { + country: 'lu', + phoneNumber: '925 527 2973', + label: 'Work', + }, + { + country: 'lu', + phoneNumber: '975 495 2977', + label: 'Home', + }, + ], + title: 'Fundraising Director', + company: 'Omnigog', + birthday: '1987-05-17T12:00:00.000Z', + address: '157 Woodhull Street, Rutherford, West Virginia, PO6646', + notes: '

Duis laboris consectetur et anim eiusmod laborum aute mollit ut officia ipsum dolore eiusmod ex eu elit officia est amet aliquip ullamco veniam proident id aliquip duis qui voluptate fugiat.

Sunt aliquip nulla amet sint culpa laboris quis proident qui veniam excepteur ullamco irure non eu occaecat est enim ut velit dolore sit tempor cillum reprehenderit proident velit lorem ad.

', + tags: ['2026ce08-d08f-4b4f-9506-b10cdb5b104f'], + }, + { + id: '012b8219-74bf-447c-af2c-66904d90a956', + avatar: 'images/avatars/female-02.jpg', + background: 'images/cards/28-640x480.jpg', + name: 'Tracy Delacruz', + emails: [ + { + email: 'tracydelacruz@mail.name', + label: 'Personal', + }, + { + email: 'delacruz.tracy@shepard.me', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'co', + phoneNumber: '974 428 2886', + label: 'Mobile', + }, + ], + title: 'Bindery Machine Operator', + company: 'Shepard', + birthday: '1963-08-10T12:00:00.000Z', + address: '604 Merit Court, Wyano, New Hampshire, PO1641', + notes: '

Dolor anim fugiat aliquip eiusmod lorem nisi adipisicing ea deserunt est quis non sit nulla voluptate deserunt magna eiusmod irure labore fugiat consectetur laboris velit voluptate exercitation aute magna sit.

Sunt ullamco quis qui ea ullamco quis sit ex nisi deserunt fugiat qui culpa minim proident dolor veniam lorem nulla amet do dolor proident sunt ex incididunt ipsum cillum non.

', + tags: ['c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309'], + }, + { + id: '8b1befd2-66a7-4981-ae52-77f01b382d18', + avatar: 'images/avatars/female-03.jpg', + background: 'images/cards/29-640x480.jpg', + name: 'Jeannette Stanton', + emails: [ + { + email: 'jeannettestanton@mail.info', + label: 'Personal', + }, + { + email: 'stanton.jeannette@zentury.biz', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'dz', + phoneNumber: '947 561 3783', + label: 'Mobile', + }, + { + country: 'dz', + phoneNumber: '917 463 3737', + label: 'Work', + }, + { + country: 'dz', + phoneNumber: '835 510 2059', + label: 'Home', + }, + ], + title: 'Hotel Manager', + company: 'Zentury', + birthday: '1975-09-02T12:00:00.000Z', + address: '100 Menahan Street, Snyderville, Kansas, PO1006', + notes: '

Sint anim sint tempor proident irure proident exercitation dolor enim in sint non occaecat tempor mollit dolore ea labore ipsum sunt in incididunt proident excepteur id in velit et quis.

Amet mollit ut nostrud cupidatat ut culpa irure in ex occaecat aute aliqua tempor incididunt elit nulla irure aliqua ea do amet ex elit incididunt minim eu fugiat elit pariatur.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '844668c3-5e20-4fed-9e3a-7d274f696e61', + avatar: 'images/avatars/female-04.jpg', + background: 'images/cards/30-640x480.jpg', + name: 'Johnnie Cleveland', + emails: [ + { + email: 'johnniecleveland@mail.co.uk', + label: 'Personal', + }, + { + email: 'cleveland.johnnie@viasia.net', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'au', + phoneNumber: '947 468 2942', + label: 'Mobile', + }, + ], + title: 'Fundraising Director', + company: 'Viasia', + birthday: '1986-03-15T12:00:00.000Z', + address: '283 Albany Avenue, Jennings, Rhode Island, PO1646', + notes: '

Id est dolore nostrud consectetur ullamco aliquip dolore nisi consectetur cupidatat consectetur ut lorem exercitation laborum est culpa qui aliquip fugiat fugiat laborum minim sint sit laborum elit consectetur occaecat.

Cillum eu aliquip ex enim dolore enim ea pariatur elit voluptate in eu magna eu voluptate est cupidatat aliqua cupidatat ex eu dolor voluptate velit fugiat ipsum labore labore aliqua.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '5a01e870-8be1-45a5-b58a-ec09c06e8f28', + avatar: 'images/avatars/female-05.jpg', + background: 'images/cards/31-640x480.jpg', + name: 'Staci Hyde', + emails: [ + { + email: 'stacihyde@mail.ca', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'id', + phoneNumber: '944 525 2944', + label: 'Mobile', + }, + { + country: 'id', + phoneNumber: '877 500 2506', + label: 'Work', + }, + ], + title: 'Banker Mason', + company: 'Zilla', + birthday: '1975-04-22T12:00:00.000Z', + address: '560 Dooley Street, Ellerslie, Louisiana, PO1005', + notes: '

Pariatur esse ex laborum ex dolor laborum proident enim consectetur occaecat magna adipisicing magna dolore officia aute et dolor aliquip enim adipisicing culpa reprehenderit aliqua officia qui pariatur aliquip occaecat.

Excepteur est nisi officia eiusmod et duis mollit labore minim duis officia lorem ipsum duis deserunt cupidatat excepteur nostrud incididunt non cillum fugiat adipisicing anim consectetur nostrud aliquip labore cupidatat.

', + tags: ['56ddbd47-4078-4ddd-8448-73c5e88d5f59'], + }, + { + id: '5ac1f193-f150-45f9-bfe4-b7b4e1a83ff9', + avatar: 'images/avatars/female-06.jpg', + background: 'images/cards/32-640x480.jpg', + name: 'Angela Gallagher', + emails: [ + { + email: 'angelagallagher@mail.org', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'et', + phoneNumber: '996 514 3856', + label: 'Mobile', + }, + { + country: 'et', + phoneNumber: '903 539 2049', + label: 'Work', + }, + { + country: 'et', + phoneNumber: '938 463 3685', + label: 'Home', + }, + ], + title: 'Electromedical Equipment Technician', + company: 'Zenolux', + birthday: '1965-08-02T12:00:00.000Z', + address: '445 Remsen Avenue, Ruckersville, Delaware, PO2712', + notes: '

Pariatur do nisi labore culpa minim aliquip excepteur voluptate id id aute eu aliquip adipisicing nulla laboris consectetur dolore ullamco ut exercitation fugiat excepteur veniam ex cillum cupidatat ad adipisicing.

Dolor culpa dolor magna incididunt voluptate sunt amet dolor cillum ut nostrud nisi quis ex pariatur enim dolore sunt sunt cupidatat id non lorem magna esse amet commodo minim id.

', + tags: ['cbde2486-5033-4e09-838e-e901b108cd41'], + }, + { + id: '995df091-d78a-4bb7-840c-ba6a7d14a1bd', + avatar: 'images/avatars/male-11.jpg', + background: 'images/cards/33-640x480.jpg', + name: 'Hutchinson Levy', + emails: [ + { + email: 'hutchinsonlevy@mail.io', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'et', + phoneNumber: '970 546 3452', + label: 'Mobile', + }, + { + country: 'et', + phoneNumber: '894 438 2430', + label: 'Work', + }, + ], + title: 'Congressional Representative', + company: 'Zytrek', + birthday: '1978-03-22T12:00:00.000Z', + address: '911 Lois Avenue, Epworth, California, PO6557', + notes: '

Veniam deserunt aliquip culpa commodo et est ea cillum ea pariatur reprehenderit dolore adipisicing voluptate dolor eiusmod tempor exercitation reprehenderit nostrud labore nostrud do nulla commodo officia qui culpa ea.

Velit deserunt do ut esse tempor minim cupidatat amet qui consequat enim duis elit veniam sunt sit aliquip irure cillum irure sunt officia incididunt cupidatat commodo amet non qui anim.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: '7184be71-a28f-4f2b-8c45-15f78cf2f825', + avatar: 'images/avatars/female-05.jpg', + background: 'images/cards/34-640x480.jpg', + name: 'Alissa Nelson', + emails: [ + { + email: 'alissanelson@mail.us', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'lu', + phoneNumber: '893 600 2639', + label: 'Mobile', + }, + ], + title: 'Bindery Machine Operator', + company: 'Emtrak', + birthday: '1993-10-19T12:00:00.000Z', + address: '514 Sutter Avenue, Shindler, Puerto Rico, PO3862', + notes: '

Ullamco ut aute reprehenderit velit incididunt veniam consequat ut ipsum sint laborum duis officia pariatur mollit enim nulla reprehenderit dolor aliquip labore ex aute in sunt dolor nulla reprehenderit dolor.

Ad enim ex non minim commodo culpa culpa ex est anim aute adipisicing proident ut ex et aliquip amet exercitation lorem tempor laborum quis reprehenderit veniam proident ullamco id eiusmod.

', + tags: ['3eaab175-ec0d-4db7-bc3b-efc633c769be'], + }, + { + id: '325d508c-ca49-42bf-b0d5-c4a6b8da3d5c', + avatar: null, + background: null, + name: 'Oliver Head', + emails: [ + { + email: 'oliverhead@mail.tv', + label: 'Personal', + }, + ], + phoneNumbers: [ + { + country: 'bn', + phoneNumber: '977 528 3294', + label: 'Mobile', + }, + ], + title: 'Meteorologist', + company: 'Rameon', + birthday: '1967-01-05T12:00:00.000Z', + address: '569 Clermont Avenue, Movico, Marshall Islands, PO7293', + notes: '

Duis laborum magna ipsum officia cillum ea ut commodo anim exercitation incididunt id ipsum nisi consectetur aute officia culpa anim in veniam ad officia consequat qui ullamco ea laboris ad.

Ad ea excepteur ea veniam nostrud est labore ea consectetur laboris cupidatat aute pariatur aute mollit dolor do deserunt nisi mollit fugiat qui officia ullamco est officia est ullamco consequat.

', + tags: ['65930b5a-5d2a-4303-b11f-865d69e6fdb5'], + }, + { + id: 'c674b6e1-b846-4bba-824b-0b4df0cdec48', + avatar: 'images/avatars/male-13.jpg', + background: 'images/cards/35-640x480.jpg', + name: 'Duran Barr', + emails: [ + { + email: 'duranbarr@mail.com', + label: 'Personal', + }, + { + email: 'barr.duran@hinway.name', + label: 'Work', + }, + ], + phoneNumbers: [ + { + country: 'sr', + phoneNumber: '857 457 2508', + label: 'Mobile', + }, + { + country: 'sr', + phoneNumber: '887 522 2146', + label: 'Work', + }, + { + country: 'sr', + phoneNumber: '947 574 3174', + label: 'Home', + }, + ], + title: 'Insurance Analyst', + company: 'Hinway', + birthday: '1977-11-06T12:00:00.000Z', + address: '103 Chestnut Avenue, Glenbrook, Indiana, PO2578', + notes: '

Ad ipsum occaecat dolore ullamco labore ex sint est pariatur aliquip ea do esse do est dolore duis excepteur esse irure eiusmod pariatur elit nostrud laboris ad ex nostrud nostrud.

Occaecat proident magna elit ullamco ea incididunt fugiat est nulla reprehenderit in veniam esse qui minim aliqua tempor excepteur dolor et tempor occaecat in veniam esse qui exercitation laborum esse.

', + tags: ['a8991c76-2fda-4bbd-a718-df13d6478847'], + }, +]; +export const countries = [ + { + id: '19430ee3-b0fe-4987-a7c8-74453ad5504d', + iso: 'af', + name: 'Afghanistan', + code: '+93', + flagImagePos: '-1px -3180px', + }, + { + id: '6c6b5c5c-97d5-4881-b5e1-e05b8f739ee7', + iso: 'al', + name: 'Albania', + code: '+355', + flagImagePos: '-1px -1310px', + }, + { + id: 'd1f3941f-075e-4777-a5fd-8b196d98cd5a', + iso: 'dz', + name: 'Algeria', + code: '+213', + flagImagePos: '-1px -681px', + }, + { + id: '0dc3d1b8-f7f3-4c3d-8493-0d8b5a679910', + iso: 'as', + name: 'American Samoa', + code: '+1', + flagImagePos: '-1px -2058px', + }, + { + id: 'e2e88578-b410-499f-aa59-9bb8da13c781', + iso: 'ad', + name: 'Andorra', + code: '+376', + flagImagePos: '-1px -766px', + }, + { + id: '4446885b-b391-4b84-866f-2b36603053c4', + iso: 'ao', + name: 'Angola', + code: '+244', + flagImagePos: '-1px -2636px', + }, + { + id: '07024099-a3db-4881-a628-24e8c0ba2508', + iso: 'ai', + name: 'Anguilla', + code: '+1', + flagImagePos: '-1px -2687px', + }, + { + id: '26be08bc-d87a-4134-9fb0-73b6a5b47cea', + iso: 'ag', + name: 'Antigua & Barbuda', + code: '+1', + flagImagePos: '-1px -1140px', + }, + { + id: '53c77399-494e-49df-9e3a-587b536c033e', + iso: 'ar', + name: 'Argentina', + code: '+54', + flagImagePos: '-1px -3282px', + }, + { + id: '9f5753c4-e9e4-4975-86b4-9eb9f4f484de', + iso: 'am', + name: 'Armenia', + code: '+374', + flagImagePos: '-1px -205px', + }, + { + id: 'f1bbb833-5c47-4e17-b8c3-1d492107dc86', + iso: 'aw', + name: 'Aruba', + code: '+297', + flagImagePos: '-1px -1021px', + }, + { + id: 'dc7e3322-8bd5-4c49-932d-a8e50bd1f9ad', + iso: 'ac', + name: 'Ascension Island', + code: '+247', + flagImagePos: '-1px -86px', + }, + { + id: '4505ba35-afa5-47ef-a6c7-9b57f1dcd187', + iso: 'au', + name: 'Australia', + code: '+61', + flagImagePos: '-1px -2279px', + }, + { + id: '57b3cd1f-d5d6-403b-8137-fbeeacaf136a', + iso: 'at', + name: 'Austria', + code: '+43', + flagImagePos: '-1px -1735px', + }, + { + id: '11cbde08-3c33-422c-bf4b-85561595ffb5', + iso: 'az', + name: 'Azerbaijan', + code: '+994', + flagImagePos: '-1px -1599px', + }, + { + id: '48c1e060-e685-4e91-8de8-725f42576e6c', + iso: 'bs', + name: 'Bahamas', + code: '+1', + flagImagePos: '-1px -460px', + }, + { + id: 'ee23ffb8-9540-4630-948e-ceba52fa54ce', + iso: 'bh', + name: 'Bahrain', + code: '+973', + flagImagePos: '-1px -1956px', + }, + { + id: 'b5f37cb6-7870-4ed9-8f92-3864bd870062', + iso: 'bd', + name: 'Bangladesh', + code: '+880', + flagImagePos: '-1px -2364px', + }, + { + id: '92de9080-f709-493e-a9fa-d23b3d4093d4', + iso: 'bb', + name: 'Barbados', + code: '+1', + flagImagePos: '-1px -2075px', + }, + { + id: 'a2f4ff04-86b8-4bc0-952f-686bfe99c07f', + iso: 'by', + name: 'Belarus', + code: '+375', + flagImagePos: '-1px -1412px', + }, + { + id: '2025b6b3-1287-4b4c-8b13-36deb44e5751', + iso: 'be', + name: 'Belgium', + code: '+32', + flagImagePos: '-1px -1px', + }, + { + id: '70d82950-3eca-496f-866d-d99c136260e5', + iso: 'bz', + name: 'Belize', + code: '+501', + flagImagePos: '-1px -613px', + }, + { + id: 'dc0bedf5-e197-46b4-af21-c2e495b15768', + iso: 'bj', + name: 'Benin', + code: '+229', + flagImagePos: '-1px -1684px', + }, + { + id: 'aeee4f9d-99a1-4c6b-826c-f3c0ff707dce', + iso: 'bm', + name: 'Bermuda', + code: '+1', + flagImagePos: '-1px -2585px', + }, + { + id: '73b80fa7-50d0-4fd5-8d26-24baade525a2', + iso: 'bt', + name: 'Bhutan', + code: '+975', + flagImagePos: '-1px -2483px', + }, + { + id: '571bf396-810b-4fc4-9ffc-c9e4db9d3bef', + iso: 'bo', + name: 'Bolivia', + code: '+591', + flagImagePos: '-1px -2177px', + }, + { + id: 'cbfbf28b-b79b-4b7d-a2e9-37a2000aa15b', + iso: 'ba', + name: 'Bosnia & Herzegovina', + code: '+387', + flagImagePos: '-1px -2092px', + }, + { + id: 'f929da82-915c-4ac8-ba13-aa1b44174c71', + iso: 'bw', + name: 'Botswana', + code: '+267', + flagImagePos: '-1px -3724px', + }, + { + id: '2dea0689-0548-400c-a58f-ebcd6373cd07', + iso: 'br', + name: 'Brazil', + code: '+55', + flagImagePos: '-1px -1004px', + }, + { + id: 'd2c2c16f-15f8-467b-8c42-a02babe5362b', + iso: 'io', + name: 'British Indian Ocean Territory', + code: '+246', + flagImagePos: '-1px -86px', + }, + { + id: '1d90db23-ca7c-4d23-a995-9b2a8021f4ad', + iso: 'vg', + name: 'British Virgin Islands', + code: '+1', + flagImagePos: '-1px -1854px', + }, + { + id: 'f16aebb2-cdae-4af2-aba5-f66f34d6ac3a', + iso: 'bn', + name: 'Brunei', + code: '+673', + flagImagePos: '-1px -2228px', + }, + { + id: '499d6ee6-8f8b-4a5b-bb92-9cce9d1c6546', + iso: 'bg', + name: 'Bulgaria', + code: '+359', + flagImagePos: '-1px -3537px', + }, + { + id: '67e2986b-98d0-44c3-b08f-6cbba8b14ff8', + iso: 'bf', + name: 'Burkina Faso', + code: '+226', + flagImagePos: '-1px -953px', + }, + { + id: 'fea611f2-4aa3-427f-86e1-657e8aef24a8', + iso: 'bi', + name: 'Burundi', + code: '+257', + flagImagePos: '-1px -2551px', + }, + { + id: '3b959360-3d04-4018-afdf-a392afa1881d', + iso: 'kh', + name: 'Cambodia', + code: '+855', + flagImagePos: '-1px -290px', + }, + { + id: '9336ba3b-01be-4b84-82b5-f02395856ac5', + iso: 'cm', + name: 'Cameroon', + code: '+237', + flagImagePos: '-1px -2806px', + }, + { + id: '36a159b0-f33e-4481-85b0-751bdd9ea79d', + iso: 'ca', + name: 'Canada', + code: '+1', + flagImagePos: '-1px -1803px', + }, + { + id: 'a3038010-382e-436e-b61d-e4b923aa1cb3', + iso: 'cv', + name: 'Cape Verde', + code: '+238', + flagImagePos: '-1px -3639px', + }, + { + id: 'dd898165-12a9-4c90-a3e4-012149c0feac', + iso: 'bq', + name: 'Caribbean Netherlands', + code: '+599', + flagImagePos: '-1px -3741px', + }, + { + id: 'a1f30091-26da-481a-a84f-2638b2d7c14d', + iso: 'ky', + name: 'Cayman Islands', + code: '+1', + flagImagePos: '-1px -375px', + }, + { + id: '469b4a79-8a1a-4428-b7bd-4665202b7292', + iso: 'cf', + name: 'Central African Republic', + code: '+236', + flagImagePos: '-1px -2466px', + }, + { + id: 'a9c2fa4b-c22a-41bd-9735-b4adeadab7f7', + iso: 'td', + name: 'Chad', + code: '+235', + flagImagePos: '-1px -1055px', + }, + { + id: 'f0825f0d-e086-49e0-846e-9e4784bf872c', + iso: 'cl', + name: 'Chile', + code: '+56', + flagImagePos: '-1px -1752px', + }, + { + id: '89d3f07d-446e-459d-b168-595af96d708f', + iso: 'cn', + name: 'China', + code: '+86', + flagImagePos: '-1px -1072px', + }, + { + id: '903801ce-2f83-4df8-a380-9dc6df6c35cf', + iso: 'co', + name: 'Colombia', + code: '+57', + flagImagePos: '-1px -409px', + }, + { + id: '55d7d2be-8273-4770-844c-1ef87524cd27', + iso: 'km', + name: 'Comoros', + code: '+269', + flagImagePos: '-1px -1871px', + }, + { + id: 'a5b00b2f-01de-4c0d-914f-fe05c92c8f43', + iso: 'cg', + name: 'Congo - Brazzaville', + code: '+242', + flagImagePos: '-1px -2398px', + }, + { + id: '58e07572-21b9-4630-a17c-a51c0ade4b8a', + iso: 'cd', + name: 'Congo - Kinshasa', + code: '+243', + flagImagePos: '-1px -1990px', + }, + { + id: '5a09d08e-b6ab-4084-8350-1d97d504c222', + iso: 'ck', + name: 'Cook Islands', + code: '+682', + flagImagePos: '-1px -3112px', + }, + { + id: '760f2b33-0822-4ad9-83cf-b497dcf273bb', + iso: 'cr', + name: 'Costa Rica', + code: '+506', + flagImagePos: '-1px -2857px', + }, + { + id: '489db55f-6316-4f43-a1c7-a0921e16743a', + iso: 'ci', + name: 'Côte d’Ivoire', + code: '+225', + flagImagePos: '-1px -2194px', + }, + { + id: '398c1d99-7ee4-44cd-9c2a-067acba2c8fb', + iso: 'hr', + name: 'Croatia', + code: '+385', + flagImagePos: '-1px -1174px', + }, + { + id: '572da7dc-8463-4797-ad84-7fcf8f53bb80', + iso: 'cu', + name: 'Cuba', + code: '+53', + flagImagePos: '-1px -987px', + }, + { + id: '572674e5-b0d4-4206-8310-70f4656e65e2', + iso: 'cw', + name: 'Curaçao', + code: '+599', + flagImagePos: '-1px -3758px', + }, + { + id: 'ac1e2a9d-a888-427e-9ad3-a0cbb27e603a', + iso: 'cy', + name: 'Cyprus', + code: '+357', + flagImagePos: '-1px -732px', + }, + { + id: '075ce3fd-83e7-472a-89cb-8b5e224102c4', + iso: 'cz', + name: 'Czechia', + code: '+420', + flagImagePos: '-1px -3095px', + }, + { + id: '4cde631a-97e9-4fc2-9465-9d9a433ca5c1', + iso: 'dk', + name: 'Denmark', + code: '+45', + flagImagePos: '-1px -1820px', + }, + { + id: '1b9c40a6-bf03-4759-b6ab-8edefafd8b44', + iso: 'dj', + name: 'Djibouti', + code: '+253', + flagImagePos: '-1px -2874px', + }, + { + id: 'f5eec2ba-1a0b-465c-b3e5-9bd8458d0704', + iso: 'dm', + name: 'Dominica', + code: '+1', + flagImagePos: '-1px -3350px', + }, + { + id: 'cb6921fc-df2a-4a97-8a34-4d901ac1e994', + iso: 'do', + name: 'Dominican Republic', + code: '+1', + flagImagePos: '-1px -2007px', + }, + { + id: '7d6641f1-ef97-4bee-b1b8-0f54fea35aeb', + iso: 'ec', + name: 'Ecuador', + code: '+593', + flagImagePos: '-1px -1531px', + }, + { + id: 'dfeb30b9-b4b8-4931-9334-c3961b7843a6', + iso: 'eg', + name: 'Egypt', + code: '+20', + flagImagePos: '-1px -3027px', + }, + { + id: '7d9f7158-7206-491f-a614-6a3e7e6af354', + iso: 'sv', + name: 'El Salvador', + code: '+503', + flagImagePos: '-1px -2160px', + }, + { + id: 'bcdbebc2-a51d-4891-93b0-52b463d0841d', + iso: 'gq', + name: 'Equatorial Guinea', + code: '+240', + flagImagePos: '-1px -1973px', + }, + { + id: '53c2c225-f321-406f-b377-7c8b6720bcb4', + iso: 'er', + name: 'Eritrea', + code: '+291', + flagImagePos: '-1px -936px', + }, + { + id: 'ba0e995a-17a8-48ff-88e6-54ff8207b038', + iso: 'ee', + name: 'Estonia', + code: '+372', + flagImagePos: '-1px -3333px', + }, + { + id: 'abe9af9b-91da-4bba-9adf-a496bf414719', + iso: 'sz', + name: 'Eswatini', + code: '+268', + flagImagePos: '-1px -3129px', + }, + { + id: 'e993ecc8-732a-4446-8ab1-144c084f3192', + iso: 'et', + name: 'Ethiopia', + code: '+251', + flagImagePos: '-1px -3367px', + }, + { + id: '6c7aae9d-e18d-4d09-8467-7bb99d925768', + iso: 'fk', + name: 'Falkland Islands (Islas Malvinas)', + code: '+500', + flagImagePos: '-1px -3809px', + }, + { + id: '92e704eb-9573-4d91-b932-2b1eddaacb3e', + iso: 'fo', + name: 'Faroe Islands', + code: '+298', + flagImagePos: '-1px -1429px', + }, + { + id: '561c079c-69c2-4e62-b947-5cd76783a67c', + iso: 'fj', + name: 'Fiji', + code: '+679', + flagImagePos: '-1px -2500px', + }, + { + id: '3f31a88e-c7ed-47fa-9aae-2058be7cbe09', + iso: 'fi', + name: 'Finland', + code: '+358', + flagImagePos: '-1px -2568px', + }, + { + id: '4c8ba1fc-0203-4a8f-8321-4dda4a0c6732', + iso: 'fr', + name: 'France', + code: '+33', + flagImagePos: '-1px -324px', + }, + { + id: '198074d5-67a2-4fd3-b13d-429a394b6371', + iso: 'gf', + name: 'French Guiana', + code: '+594', + flagImagePos: '-1px -324px', + }, + { + id: '2f5ff3d1-745e-48a1-b4e8-a377b22af812', + iso: 'pf', + name: 'French Polynesia', + code: '+689', + flagImagePos: '-1px -2262px', + }, + { + id: 'a8b80121-5529-4cfe-83fb-6b1f6c81abcb', + iso: 'ga', + name: 'Gabon', + code: '+241', + flagImagePos: '-1px -1157px', + }, + { + id: 'c9bc7d57-7883-4f63-bc6e-5dcc3db8612d', + iso: 'gm', + name: 'Gambia', + code: '+220', + flagImagePos: '-1px -817px', + }, + { + id: '1fc146d8-cebe-4ef1-bb0f-30bd0870ccf9', + iso: 'ge', + name: 'Georgia', + code: '+995', + flagImagePos: '-1px -1123px', + }, + { + id: 'e74ac4b1-0b4b-4630-bac0-2e53e270b363', + iso: 'de', + name: 'Germany', + code: '+49', + flagImagePos: '-1px -3452px', + }, + { + id: 'adda89c9-4b47-4552-85c4-668f2cef2dbd', + iso: 'gh', + name: 'Ghana', + code: '+233', + flagImagePos: '-1px -2891px', + }, + { + id: '962a059b-a5ac-4e2f-9405-5c418cadb6b0', + iso: 'gi', + name: 'Gibraltar', + code: '+350', + flagImagePos: '-1px -341px', + }, + { + id: '0acd0dae-0f39-4c23-be1d-c0295539d8c4', + iso: 'gr', + name: 'Greece', + code: '+30', + flagImagePos: '-1px -188px', + }, + { + id: '7529a6e4-8a6a-4c27-885e-ff0c5e15e515', + iso: 'gl', + name: 'Greenland', + code: '+299', + flagImagePos: '-1px -2347px', + }, + { + id: '416ba85d-f860-48dc-9c60-32602c07e266', + iso: 'gd', + name: 'Grenada', + code: '+1', + flagImagePos: '-1px -3316px', + }, + { + id: 'f43f1f96-1fb1-4e5e-b818-71e60e501fd4', + iso: 'gp', + name: 'Guadeloupe', + code: '+590', + flagImagePos: '-1px -511px', + }, + { + id: 'e29122da-20cf-4d24-bc68-93f9c3296730', + iso: 'gu', + name: 'Guam', + code: '+1', + flagImagePos: '-1px -3265px', + }, + { + id: '8a24ff28-dcae-4846-b0c1-18cfcb04de06', + iso: 'gt', + name: 'Guatemala', + code: '+502', + flagImagePos: '-1px -1208px', + }, + { + id: 'b617a005-be15-49c8-9533-c0376681a564', + iso: 'gn', + name: 'Guinea', + code: '+224', + flagImagePos: '-1px -3520px', + }, + { + id: 'd9913e74-e340-4a4f-bf4b-aaaf1747364b', + iso: 'gw', + name: 'Guinea-Bissau', + code: '+245', + flagImagePos: '-1px -2602px', + }, + { + id: 'c8245da4-cd4f-4818-a41e-42afec6faa9a', + iso: 'gy', + name: 'Guyana', + code: '+592', + flagImagePos: '-1px -1038px', + }, + { + id: 'c598961d-3040-4dbb-8934-6d8eb4b9be97', + iso: 'ht', + name: 'Haiti', + code: '+509', + flagImagePos: '-1px -392px', + }, + { + id: 'f51aadf1-3c7a-4d24-b8fb-69c7e05243e4', + iso: 'hn', + name: 'Honduras', + code: '+504', + flagImagePos: '-1px -2959px', + }, + { + id: 'a621dbe5-fb11-4f7f-9a8d-2330bd20c563', + iso: 'hk', + name: 'Hong Kong', + code: '+852', + flagImagePos: '-1px -3707px', + }, + { + id: 'a113fe26-d409-4ab7-b27c-0e8ac112071f', + iso: 'hu', + name: 'Hungary', + code: '+36', + flagImagePos: '-1px -902px', + }, + { + id: '6430b612-4071-4614-bfdb-408fbb0b8fa4', + iso: 'is', + name: 'Iceland', + code: '+354', + flagImagePos: '-1px -2704px', + }, + { + id: '4cce1334-df1f-4b11-9f15-a4faaac3d0db', + iso: 'in', + name: 'India', + code: '+91', + flagImagePos: '-1px -2245px', + }, + { + id: '54969b2f-6aa9-4a58-850d-b4779ef3038e', + iso: 'id', + name: 'Indonesia', + code: '+62', + flagImagePos: '-1px -2653px', + }, + { + id: 'cb631628-5854-44d2-9dbc-47cdf9c9ea5e', + iso: 'ir', + name: 'Iran', + code: '+98', + flagImagePos: '-1px -2738px', + }, + { + id: '21a50cc1-954c-49c2-8296-696f1f57b79e', + iso: 'iq', + name: 'Iraq', + code: '+964', + flagImagePos: '-1px -851px', + }, + { + id: '3e17cb8a-9c44-4c75-b417-556546ceebff', + iso: 'ie', + name: 'Ireland', + code: '+353', + flagImagePos: '-1px -2670px', + }, + { + id: '0a15f5a3-7571-478a-9fcd-6cbd6563e08c', + iso: 'il', + name: 'Israel', + code: '+972', + flagImagePos: '-1px -426px', + }, + { + id: '2cbab786-d79b-4ea1-ab26-0553c5e423d3', + iso: 'it', + name: 'Italy', + code: '+39', + flagImagePos: '-1px -154px', + }, + { + id: '33a67cd8-0858-46c3-b833-4fd395d2daa4', + iso: 'jm', + name: 'Jamaica', + code: '+1', + flagImagePos: '-1px -2296px', + }, + { + id: '5edf8bb6-6a29-44ee-b5f2-7d7cbf61f971', + iso: 'jp', + name: 'Japan', + code: '+81', + flagImagePos: '-1px -528px', + }, + { + id: '879b69bb-3f8f-484f-a767-7fdeef6bae15', + iso: 'jo', + name: 'Jordan', + code: '+962', + flagImagePos: '-1px -1905px', + }, + { + id: '4217e52c-2835-4c7b-87d3-e290c4fa6074', + iso: 'kz', + name: 'Kazakhstan', + code: '+7', + flagImagePos: '-1px -1565px', + }, + { + id: '934b172d-4427-47f6-8648-6411652be23d', + iso: 'ke', + name: 'Kenya', + code: '+254', + flagImagePos: '-1px -3605px', + }, + { + id: '2358e177-3956-4bcf-a954-56275e90e28d', + iso: 'ki', + name: 'Kiribati', + code: '+686', + flagImagePos: '-1px -477px', + }, + { + id: '98e8fae8-cd1b-419f-813b-ee348b51d843', + iso: 'xk', + name: 'Kosovo', + code: '+383', + flagImagePos: '-1px -3860px', + }, + { + id: '5376f774-4fcb-47dc-b118-e48d34b030ef', + iso: 'kw', + name: 'Kuwait', + code: '+965', + flagImagePos: '-1px -3435px', + }, + { + id: '9bc380c4-5840-4d26-a615-310cd817ae94', + iso: 'kg', + name: 'Kyrgyzstan', + code: '+996', + flagImagePos: '-1px -2143px', + }, + { + id: '3278e7f0-176b-4352-9e38-df59b052b91f', + iso: 'la', + name: 'Laos', + code: '+856', + flagImagePos: '-1px -562px', + }, + { + id: 'e2ba5fad-f531-467c-b195-a6cd90136e19', + iso: 'lv', + name: 'Latvia', + code: '+371', + flagImagePos: '-1px -2619px', + }, + { + id: '49f74ca5-9ff1-44af-8e9c-59e1c4704e83', + iso: 'lb', + name: 'Lebanon', + code: '+961', + flagImagePos: '-1px -1616px', + }, + { + id: 'd94b6d96-17c1-4de8-abc3-3e14873b62c0', + iso: 'ls', + name: 'Lesotho', + code: '+266', + flagImagePos: '-1px -3010px', + }, + { + id: 'e35005f8-285e-4fe5-9cda-def721d9cc7b', + iso: 'lr', + name: 'Liberia', + code: '+231', + flagImagePos: '-1px -2823px', + }, + { + id: '60788779-78f0-4b2b-8ad8-c7e4bbde10b5', + iso: 'ly', + name: 'Libya', + code: '+218', + flagImagePos: '-1px -137px', + }, + { + id: 'f24ad4ea-454a-4d40-a1f1-db188ec0b75e', + iso: 'li', + name: 'Liechtenstein', + code: '+423', + flagImagePos: '-1px -1276px', + }, + { + id: 'f6709b72-4150-4cde-a37b-e6eb95f5bd1d', + iso: 'lt', + name: 'Lithuania', + code: '+370', + flagImagePos: '-1px -1446px', + }, + { + id: '0d0c1a84-f645-4ffe-87d2-9a7bb4f88bbc', + iso: 'lu', + name: 'Luxembourg', + code: '+352', + flagImagePos: '-1px -1922px', + }, + { + id: '5b3fdebe-a4ed-47c6-88c3-d867d3a79bf0', + iso: 'mo', + name: 'Macao', + code: '+853', + flagImagePos: '-1px -3554px', + }, + { + id: '6a84f456-bc77-4b76-8651-e2a0994f3278', + iso: 'mg', + name: 'Madagascar', + code: '+261', + flagImagePos: '-1px -1667px', + }, + { + id: '2a5d5baf-1db7-4606-a330-227834c77098', + iso: 'mw', + name: 'Malawi', + code: '+265', + flagImagePos: '-1px -2942px', + }, + { + id: 'f2b32090-6d8d-40db-ba50-a63037926508', + iso: 'my', + name: 'Malaysia', + code: '+60', + flagImagePos: '-1px -2517px', + }, + { + id: '51c7830c-0c76-44ed-bcdf-be75688e1d0c', + iso: 'mv', + name: 'Maldives', + code: '+960', + flagImagePos: '-1px -800px', + }, + { + id: 'ea7a2274-0542-4bbb-b629-aa63bef97442', + iso: 'ml', + name: 'Mali', + code: '+223', + flagImagePos: '-1px -3469px', + }, + { + id: '6f70796e-8f64-4a1a-ac2a-990d7d502db3', + iso: 'mt', + name: 'Malta', + code: '+356', + flagImagePos: '-1px -2041px', + }, + { + id: 'c60f429e-0d4f-42cf-96f9-e7dc4fdcd5ee', + iso: 'mh', + name: 'Marshall Islands', + code: '+692', + flagImagePos: '-1px -1463px', + }, + { + id: 'e8afae89-e5b0-4551-bbd4-bbfcee50c8ad', + iso: 'mq', + name: 'Martinique', + code: '+596', + flagImagePos: '-1px -239px', + }, + { + id: '361afc7c-ee94-464b-b5cb-f059ecd79e99', + iso: 'mr', + name: 'Mauritania', + code: '+222', + flagImagePos: '-1px -307px', + }, + { + id: 'bce43b5e-d2f7-47ca-b5c9-9ae72ba67bda', + iso: 'mu', + name: 'Mauritius', + code: '+230', + flagImagePos: '-1px -2993px', + }, + { + id: 'd153dc32-4821-4f05-a5c8-564d003da5e1', + iso: 'mx', + name: 'Mexico', + code: '+52', + flagImagePos: '-1px -2755px', + }, + { + id: '80f9f386-231f-4d96-b950-5f6b6edbeb63', + iso: 'fm', + name: 'Micronesia', + code: '+691', + flagImagePos: '-1px -2313px', + }, + { + id: 'a1d89e32-4b91-4519-b0d9-7d61299394ef', + iso: 'md', + name: 'Moldova', + code: '+373', + flagImagePos: '-1px -3690px', + }, + { + id: '0afeb22c-c106-479b-af45-1380fb8b404c', + iso: 'mc', + name: 'Monaco', + code: '+377', + flagImagePos: '-1px -1191px', + }, + { + id: 'a18d0204-7c4a-425c-a33e-cbfac01be162', + iso: 'mn', + name: 'Mongolia', + code: '+976', + flagImagePos: '-1px -3503px', + }, + { + id: '260479fc-0410-4ccd-a963-e06c9f059bdb', + iso: 'me', + name: 'Montenegro', + code: '+382', + flagImagePos: '-1px -2976px', + }, + { + id: 'a66872f1-ba90-420f-8f55-f0fbb10abce1', + iso: 'ms', + name: 'Montserrat', + code: '+1', + flagImagePos: '-1px -749px', + }, + { + id: '8fd1ba13-cb1a-488d-b715-01724d56d9dd', + iso: 'ma', + name: 'Morocco', + code: '+212', + flagImagePos: '-1px -3214px', + }, + { + id: '5d26fba4-6d15-4cd4-a23f-9034d952e580', + iso: 'mz', + name: 'Mozambique', + code: '+258', + flagImagePos: '-1px -834px', + }, + { + id: 'f9c12031-14dc-495f-b150-28dddce17e3f', + iso: 'mm', + name: 'Myanmar (Burma)', + code: '+95', + flagImagePos: '-1px -18px', + }, + { + id: '6e21e956-2740-4058-a758-3b249f628a7b', + iso: 'na', + name: 'Namibia', + code: '+264', + flagImagePos: '-1px -2534px', + }, + { + id: '4a07dd5a-9341-4b06-969f-4bcd9c32e2a0', + iso: 'nr', + name: 'Nauru', + code: '+674', + flagImagePos: '-1px -2330px', + }, + { + id: '9d7121ce-1445-4c84-9401-ddc703d9dedb', + iso: 'np', + name: 'Nepal', + code: '+977', + flagImagePos: '-1px -120px', + }, + { + id: '31fbb24d-7c38-4ca8-b385-48d76a0685e3', + iso: 'nl', + name: 'Netherlands', + code: '+31', + flagImagePos: '-1px -1888px', + }, + { + id: '18071cc2-c457-4b4f-9217-2519a0b52c25', + iso: 'nc', + name: 'New Caledonia', + code: '+687', + flagImagePos: '-1px -1650px', + }, + { + id: 'c4b0e7d1-08b2-421b-8ff6-913020cbf271', + iso: 'nz', + name: 'New Zealand', + code: '+64', + flagImagePos: '-1px -2024px', + }, + { + id: '25719230-2c64-4525-96c4-d4427dd2e40b', + iso: 'ni', + name: 'Nicaragua', + code: '+505', + flagImagePos: '-1px -171px', + }, + { + id: 'a1090a0b-7f89-4d75-8c92-e460da9103ab', + iso: 'ne', + name: 'Niger', + code: '+227', + flagImagePos: '-1px -715px', + }, + { + id: '6869e4bb-32b8-43ff-84d1-67d9ee832e1f', + iso: 'ng', + name: 'Nigeria', + code: '+234', + flagImagePos: '-1px -3418px', + }, + { + id: '52b3ae35-196a-4e22-81e2-67b816a32d0e', + iso: 'nu', + name: 'Niue', + code: '+683', + flagImagePos: '-1px -2840px', + }, + { + id: '9f4e45d4-c7e1-4ba9-84d0-e712e7213c95', + iso: 'nf', + name: 'Norfolk Island', + code: '+672', + flagImagePos: '-1px -256px', + }, + { + id: '2db1b02c-631e-40a0-94d8-f1e567b1f705', + iso: 'kp', + name: 'North Korea', + code: '+850', + flagImagePos: '-1px -2415px', + }, + { + id: '92621b3f-55f5-42bb-8604-d0302e355e31', + iso: 'mk', + name: 'North Macedonia', + code: '+389', + flagImagePos: '-1px -1769px', + }, + { + id: '3cee8ab2-5cb3-43ea-b8ab-7016187d33e9', + iso: 'mp', + name: 'Northern Mariana Islands', + code: '+1', + flagImagePos: '-1px -919px', + }, + { + id: '77683fad-f106-4a94-a629-9562650edb35', + iso: 'no', + name: 'Norway', + code: '+47', + flagImagePos: '-1px -1089px', + }, + { + id: '09090411-ef9b-44f3-aeb9-65b5e338b8d6', + iso: 'om', + name: 'Oman', + code: '+968', + flagImagePos: '-1px -3384px', + }, + { + id: '18d4f06b-233b-4398-a9f8-6b4a4eaf6c71', + iso: 'pk', + name: 'Pakistan', + code: '+92', + flagImagePos: '-1px -2772px', + }, + { + id: 'b1da5023-aab9-431c-921c-4f3e12b1aa7a', + iso: 'pw', + name: 'Palau', + code: '+680', + flagImagePos: '-1px -273px', + }, + { + id: 'e6442ab2-ac99-4a02-9d7c-fd878e50de8a', + iso: 'ps', + name: 'Palestine', + code: '+970', + flagImagePos: '-1px -1548px', + }, + { + id: '6bb10fb5-8b4a-4136-a82e-6be6c017ab76', + iso: 'pa', + name: 'Panama', + code: '+507', + flagImagePos: '-1px -1106px', + }, + { + id: 'b070a014-2ce4-4939-a868-951bd1e70923', + iso: 'pg', + name: 'Papua New Guinea', + code: '+675', + flagImagePos: '-1px -1939px', + }, + { + id: '5e23c743-ce7d-4abc-9dd4-44a700b29090', + iso: 'py', + name: 'Paraguay', + code: '+595', + flagImagePos: '-1px -3231px', + }, + { + id: '1a83f99d-91b3-438d-a576-5bf0f05fdd12', + iso: 'pe', + name: 'Peru', + code: '+51', + flagImagePos: '-1px -1225px', + }, + { + id: '667c9699-46b9-40f9-a41f-2c52826bb3cb', + iso: 'ph', + name: 'Philippines', + code: '+63', + flagImagePos: '-1px -2432px', + }, + { + id: 'b84030ab-3193-4aa2-aef2-d4d21997e536', + iso: 'pl', + name: 'Poland', + code: '+48', + flagImagePos: '-1px -1514px', + }, + { + id: 'e26d0064-6173-42ab-b761-bf8c639199fa', + iso: 'pt', + name: 'Portugal', + code: '+351', + flagImagePos: '-1px -664px', + }, + { + id: '0fd9770d-2a91-4b81-8633-f465bc151e16', + iso: 'pr', + name: 'Puerto Rico', + code: '+1', + flagImagePos: '-1px -596px', + }, + { + id: 'f866eeeb-e64f-4123-ab63-c16e0a00d029', + iso: 'qa', + name: 'Qatar', + code: '+974', + flagImagePos: '-1px -579px', + }, + { + id: 'c3a3fb54-5731-4a28-96bd-4190cfeeaff0', + iso: 're', + name: 'Réunion', + code: '+262', + flagImagePos: '-1px -324px', + }, + { + id: 'a6a48809-7e33-42c8-a25a-56ccdd7ccdfe', + iso: 'ro', + name: 'Romania', + code: '+40', + flagImagePos: '-1px -885px', + }, + { + id: '9556d1e9-3d02-4c5b-a0ce-97a2fd55c74a', + iso: 'ru', + name: 'Russia', + code: '+7', + flagImagePos: '-1px -868px', + }, + { + id: '6f7f0a97-e8b5-455d-bace-6953de7324eb', + iso: 'rw', + name: 'Rwanda', + code: '+250', + flagImagePos: '-1px -3673px', + }, + { + id: 'e251cad5-7655-48f7-9892-6edf04a14fd7', + iso: 'ws', + name: 'Samoa', + code: '+685', + flagImagePos: '-1px -3163px', + }, + { + id: 'f1cfec8c-a960-43b3-8e11-2cad72b4fff8', + iso: 'sm', + name: 'San Marino', + code: '+378', + flagImagePos: '-1px -2908px', + }, + { + id: 'c5301260-13dc-4012-9678-2b57a5e409ae', + iso: 'st', + name: 'São Tomé & Príncipe', + code: '+239', + flagImagePos: '-1px -3299px', + }, + { + id: '02599f80-225a-451b-8c25-03b8993f88ac', + iso: 'sa', + name: 'Saudi Arabia', + code: '+966', + flagImagePos: '-1px -52px', + }, + { + id: 'a54c3469-9668-4063-bfa0-04c450b43d3e', + iso: 'sn', + name: 'Senegal', + code: '+221', + flagImagePos: '-1px -2925px', + }, + { + id: '687ea07b-a7df-4778-b802-b040676fa56c', + iso: 'rs', + name: 'Serbia', + code: '+381', + flagImagePos: '-1px -3401px', + }, + { + id: 'd010fb25-7044-4055-9c60-25bc89d83f64', + iso: 'sc', + name: 'Seychelles', + code: '+248', + flagImagePos: '-1px -1327px', + }, + { + id: '0c46a1e9-fcd8-4e7e-bbb1-ef3bfa83539b', + iso: 'sl', + name: 'Sierra Leone', + code: '+232', + flagImagePos: '-1px -970px', + }, + { + id: 'e724edb6-9df4-42fb-bc1e-417996aa3020', + iso: 'sg', + name: 'Singapore', + code: '+65', + flagImagePos: '-1px -35px', + }, + { + id: '7478814a-dc3f-41ff-9341-da7e07ba8499', + iso: 'sx', + name: 'Sint Maarten', + code: '+1', + flagImagePos: '-1px -3826px', + }, + { + id: 'b1a34e32-38dd-4a38-b63a-7133baf1417a', + iso: 'sk', + name: 'Slovakia', + code: '+421', + flagImagePos: '-1px -3044px', + }, + { + id: '1c1689a5-580b-411f-9283-b1e8333b351e', + iso: 'si', + name: 'Slovenia', + code: '+386', + flagImagePos: '-1px -1582px', + }, + { + id: '4b1c6a42-90b0-49ea-b968-8c95b871f0ec', + iso: 'sb', + name: 'Solomon Islands', + code: '+677', + flagImagePos: '-1px -1361px', + }, + { + id: '7ec9fdff-8ae6-4a14-b55e-6262d46bc3ef', + iso: 'so', + name: 'Somalia', + code: '+252', + flagImagePos: '-1px -1786px', + }, + { + id: '5e62f404-3e2c-4d63-ad7b-ab0755903842', + iso: 'za', + name: 'South Africa', + code: '+27', + flagImagePos: '-1px -3248px', + }, + { + id: '31966c2a-7d24-4ebc-8e02-392e4f04f12b', + iso: 'kr', + name: 'South Korea', + code: '+82', + flagImagePos: '-1px -3078px', + }, + { + id: '1b7ba825-bf7d-42c0-bb73-81f10a4009bf', + iso: 'ss', + name: 'South Sudan', + code: '+211', + flagImagePos: '-1px -3775px', + }, + { + id: '55c4137b-e437-4e80-bc8f-7857cd7c9364', + iso: 'es', + name: 'Spain', + code: '+34', + flagImagePos: '-1px -1480px', + }, + { + id: 'fce4c284-e6a1-4e8c-96ca-6edf09e8a401', + iso: 'lk', + name: 'Sri Lanka', + code: '+94', + flagImagePos: '-1px -3622px', + }, + { + id: '0ae719a5-ae43-45d0-b669-66976a050ef1', + iso: 'bl', + name: 'St. Barthélemy', + code: '+590', + flagImagePos: '-1px -324px', + }, + { + id: 'a588cc85-32a4-45ff-ba69-627105dab27a', + iso: 'sh', + name: 'St. Helena', + code: '+290', + flagImagePos: '-1px -630px', + }, + { + id: 'f065aa7c-8d9e-419c-bbf0-9a97011cf272', + iso: 'kn', + name: 'St. Kitts & Nevis', + code: '+1', + flagImagePos: '-1px -103px', + }, + { + id: '9ea73bcc-2bf5-4ad9-9b39-de33de125f98', + iso: 'lc', + name: 'St. Lucia', + code: '+1', + flagImagePos: '-1px -1837px', + }, + { + id: '86a5a0e8-bfd4-480e-9bc0-7b88b2248a57', + iso: 'mf', + name: 'St. Martin', + code: '+590', + flagImagePos: '-1px -86px', + }, + { + id: '540857ba-923a-4656-a19f-cb3914825ecc', + iso: 'pm', + name: 'St. Pierre & Miquelon', + code: '+508', + flagImagePos: '-1px -1378px', + }, + { + id: 'd381eb44-e77a-4dbd-abbb-224d7158e96d', + iso: 'vc', + name: 'St. Vincent & Grenadines', + code: '+1', + flagImagePos: '-1px -3588px', + }, + { + id: '7015db62-072d-49a2-8320-7587ec8b952f', + iso: 'sd', + name: 'Sudan', + code: '+249', + flagImagePos: '-1px -443px', + }, + { + id: 'd7bbb285-aa4e-4a92-8613-8d2645c351ee', + iso: 'sr', + name: 'Suriname', + code: '+597', + flagImagePos: '-1px -3656px', + }, + { + id: '78978092-7be3-4ec8-b201-068089035cff', + iso: 'se', + name: 'Sweden', + code: '+46', + flagImagePos: '-1px -494px', + }, + { + id: '9f3fbec3-b58a-4b5a-9c4b-3997398c4148', + iso: 'ch', + name: 'Switzerland', + code: '+41', + flagImagePos: '-1px -1718px', + }, + { + id: '7ce0562c-fdc4-444c-bba3-02239c3c17da', + iso: 'sy', + name: 'Syria', + code: '+963', + flagImagePos: '-1px -2449px', + }, + { + id: '2d57a4a1-3f5a-41a2-a320-74a8f0db92e5', + iso: 'tw', + name: 'Taiwan', + code: '+886', + flagImagePos: '-1px -647px', + }, + { + id: 'e1f747c5-4e91-487b-8265-8f70b3430849', + iso: 'tj', + name: 'Tajikistan', + code: '+992', + flagImagePos: '-1px -222px', + }, + { + id: 'f07e257c-e049-4046-b031-f4348fb1734a', + iso: 'tz', + name: 'Tanzania', + code: '+255', + flagImagePos: '-1px -3146px', + }, + { + id: '684a0dde-5b5f-4072-98a4-46fc8de09556', + iso: 'th', + name: 'Thailand', + code: '+66', + flagImagePos: '-1px -1242px', + }, + { + id: '0376e29f-d9dd-4449-aa4e-d47353c16873', + iso: 'tl', + name: 'Timor-Leste', + code: '+670', + flagImagePos: '-1px -3843px', + }, + { + id: 'fd647814-fc64-4724-bba7-4cd4da26c11e', + iso: 'tg', + name: 'Togo', + code: '+228', + flagImagePos: '-1px -783px', + }, + { + id: 'ed271b14-39ee-4403-9be6-b54ac89b0ed3', + iso: 'tk', + name: 'Tokelau', + code: '+690', + flagImagePos: '-1px -3792px', + }, + { + id: 'e2b83ecb-5a79-4ca0-9860-4baeae0380bb', + iso: 'to', + name: 'Tonga', + code: '+676', + flagImagePos: '-1px -1395px', + }, + { + id: '33bca09c-cc33-4680-929b-191ccbbc959a', + iso: 'tt', + name: 'Trinidad & Tobago', + code: '+1', + flagImagePos: '-1px -545px', + }, + { + id: 'ab25c5da-7698-4b96-af34-5d20523915d9', + iso: 'tn', + name: 'Tunisia', + code: '+216', + flagImagePos: '-1px -698px', + }, + { + id: '784ac645-bc50-4b35-b5fb-effd72f99749', + iso: 'tr', + name: 'Turkey', + code: '+90', + flagImagePos: '-1px -2126px', + }, + { + id: '9a3b8bd3-bc73-4251-a068-a4842365e91a', + iso: 'tm', + name: 'Turkmenistan', + code: '+993', + flagImagePos: '-1px -3486px', + }, + { + id: '361bcad4-44d1-41fb-9bbf-39ea0fb87d49', + iso: 'tc', + name: 'Turks & Caicos Islands', + code: '+1', + flagImagePos: '-1px -1701px', + }, + { + id: '26fb1484-c756-4592-8523-99af9c870bb5', + iso: 'tv', + name: 'Tuvalu', + code: '+688', + flagImagePos: '-1px -358px', + }, + { + id: 'cdb8455e-4eda-48f7-b30a-63c20838a364', + iso: 'vi', + name: 'U.S. Virgin Islands', + code: '+1', + flagImagePos: '-1px -2381px', + }, + { + id: 'f47476cc-3da6-4377-83c9-33ab9f5293d1', + iso: 'ug', + name: 'Uganda', + code: '+256', + flagImagePos: '-1px -1497px', + }, + { + id: '5fcb791a-91be-416a-895d-0502fc509838', + iso: 'ua', + name: 'Ukraine', + code: '+380', + flagImagePos: '-1px -2721px', + }, + { + id: '7c8e1ced-0dd7-42b6-880b-19b3486d11e5', + iso: 'ae', + name: 'United Arab Emirates', + code: '+971', + flagImagePos: '-1px -3061px', + }, + { + id: '9f1362e7-e87c-4123-ade8-e5cfa6e99c09', + iso: 'gb', + name: 'United Kingdom', + code: '+44', + flagImagePos: '-1px -86px', + }, + { + id: 'f9033267-9df0-46e4-9f79-c8b022e5c835', + iso: 'us', + name: 'United States', + code: '+1', + flagImagePos: '-1px -69px', + }, + { + id: '2cab7122-ec9a-48ac-8415-392b4f67ae51', + iso: 'uy', + name: 'Uruguay', + code: '+598', + flagImagePos: '-1px -3571px', + }, + { + id: 'f442740c-94c3-4f2f-afb2-c7c279224b5f', + iso: 'uz', + name: 'Uzbekistan', + code: '+998', + flagImagePos: '-1px -1293px', + }, + { + id: 'e6774547-6ab1-41a2-8107-201f913937b2', + iso: 'vu', + name: 'Vanuatu', + code: '+678', + flagImagePos: '-1px -1633px', + }, + { + id: 'd600d6b0-e21f-4b6e-9036-0435a6ac2ea6', + iso: 'va', + name: 'Vatican City', + code: '+39', + flagImagePos: '-1px -3197px', + }, + { + id: 'b8e0072d-498b-4bb4-a5b6-354d4200f882', + iso: 've', + name: 'Venezuela', + code: '+58', + flagImagePos: '-1px -1344px', + }, + { + id: '15dc081a-4690-42e9-a40d-b3bcea3173fc', + iso: 'vn', + name: 'Vietnam', + code: '+84', + flagImagePos: '-1px -1259px', + }, + { + id: '4452a787-5f31-4eb7-b14c-ae3175564ae5', + iso: 'wf', + name: 'Wallis & Futuna', + code: '+681', + flagImagePos: '-1px -324px', + }, + { + id: '237c9f8d-3b6c-4b70-af72-8a58a7154144', + iso: 'ye', + name: 'Yemen', + code: '+967', + flagImagePos: '-1px -2211px', + }, + { + id: '02a76f62-3078-472a-bd42-edb759cf3079', + iso: 'zm', + name: 'Zambia', + code: '+260', + flagImagePos: '-1px -2109px', + }, + { + id: '10e8e117-6832-4d3f-9b05-f66832c2f5ec', + iso: 'zw', + name: 'Zimbabwe', + code: '+263', + flagImagePos: '-1px -2789px', + }, +]; +export const tags = [ + { + id: 'c31e9e5d-e0cb-4574-a13f-8a6ee5ff8309', + title: 'Work', + }, + { + id: 'a8991c76-2fda-4bbd-a718-df13d6478847', + title: 'Friend', + }, + { + id: '56ddbd47-4078-4ddd-8448-73c5e88d5f59', + title: 'Family', + }, + { + id: '2026ce08-d08f-4b4f-9506-b10cdb5b104f', + title: 'High School', + }, + { + id: '65930b5a-5d2a-4303-b11f-865d69e6fdb5', + title: 'College', + }, + { + id: '3eaab175-ec0d-4db7-bc3b-efc633c769be', + title: 'Baseball Team', + }, + { + id: 'cbde2486-5033-4e09-838e-e901b108cd41', + title: 'Band', + }, +]; diff --git a/src/app/mock-api/apps/ecommerce/inventory/api.ts b/src/app/mock-api/apps/ecommerce/inventory/api.ts new file mode 100644 index 0000000..2c4ee72 --- /dev/null +++ b/src/app/mock-api/apps/ecommerce/inventory/api.ts @@ -0,0 +1,332 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api'; +import { + brands as brandsData, + categories as categoriesData, + products as productsData, + tags as tagsData, + vendors as vendorsData, +} from 'app/mock-api/apps/ecommerce/inventory/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class ECommerceInventoryMockApi { + private _categories: any[] = categoriesData; + private _brands: any[] = brandsData; + private _products: any[] = productsData; + private _tags: any[] = tagsData; + private _vendors: any[] = vendorsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Categories - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/categories') + .reply(() => [200, cloneDeep(this._categories)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Brands - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/brands') + .reply(() => [200, cloneDeep(this._brands)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Products - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/products', 300) + .reply(({ request }) => { + // Get available queries + const search = request.params.get('search'); + const sort = request.params.get('sort') || 'name'; + const order = request.params.get('order') || 'asc'; + const page = parseInt(request.params.get('page') ?? '1', 10); + const size = parseInt(request.params.get('size') ?? '10', 10); + + // Clone the products + let products: any[] | null = cloneDeep(this._products); + + // Sort the products + if (sort === 'sku' || sort === 'name' || sort === 'active') { + products.sort((a, b) => { + const fieldA = a[sort].toString().toUpperCase(); + const fieldB = b[sort].toString().toUpperCase(); + return order === 'asc' + ? fieldA.localeCompare(fieldB) + : fieldB.localeCompare(fieldA); + }); + } else { + products.sort((a, b) => + order === 'asc' ? a[sort] - b[sort] : b[sort] - a[sort] + ); + } + + // If search exists... + if (search) { + // Filter the products + products = products.filter( + (contact) => + contact.name && + contact.name + .toLowerCase() + .includes(search.toLowerCase()) + ); + } + + // Paginate - Start + const productsLength = products.length; + + // Calculate pagination details + const begin = page * size; + const end = Math.min(size * (page + 1), productsLength); + const lastPage = Math.max(Math.ceil(productsLength / size), 1); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // products but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + products = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by size + products = products.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + length: productsLength, + size: size, + page: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + products, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Product - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/product') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the products + const products = cloneDeep(this._products); + + // Find the product + const product = products.find((item) => item.id === id); + + // Return the response + return [200, product]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Product - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/ecommerce/inventory/product') + .reply(() => { + // Generate a new product + const newProduct = { + id: AngorMockApiUtils.guid(), + category: '', + name: 'A New Product', + description: '', + tags: [], + sku: '', + barcode: '', + brand: '', + vendor: '', + stock: '', + reserved: '', + cost: '', + basePrice: '', + taxPercent: '', + price: '', + weight: '', + thumbnail: '', + images: [], + active: false, + }; + + // Unshift the new product + this._products.unshift(newProduct); + + // Return the response + return [200, newProduct]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Product - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/ecommerce/inventory/product') + .reply(({ request }) => { + // Get the id and product + const id = request.body.id; + const product = cloneDeep(request.body.product); + + // Prepare the updated product + let updatedProduct = null; + + // Find the product and update it + this._products.forEach((item, index, products) => { + if (item.id === id) { + // Update the product + products[index] = assign({}, products[index], product); + + // Store the updated product + updatedProduct = products[index]; + } + }); + + // Return the response + return [200, updatedProduct]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Product - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/ecommerce/inventory/product') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the product and delete it + this._products.forEach((item, index) => { + if (item.id === id) { + this._products.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/tags') + .reply(() => [200, cloneDeep(this._tags)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/ecommerce/inventory/tag') + .reply(({ request }) => { + // Get the tag + const newTag = cloneDeep(request.body.tag); + + // Generate a new GUID + newTag.id = AngorMockApiUtils.guid(); + + // Unshift the new tag + this._tags.unshift(newTag); + + // Return the response + return [200, newTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/ecommerce/inventory/tag') + .reply(({ request }) => { + // Get the id and tag + const id = request.body.id; + const tag = cloneDeep(request.body.tag); + + // Prepare the updated tag + let updatedTag = null; + + // Find the tag and update it + this._tags.forEach((item, index, tags) => { + if (item.id === id) { + // Update the tag + tags[index] = assign({}, tags[index], tag); + + // Store the updated tag + updatedTag = tags[index]; + } + }); + + // Return the response + return [200, updatedTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tag - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/ecommerce/inventory/tag') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the tag and delete it + this._tags.forEach((item, index) => { + if (item.id === id) { + this._tags.splice(index, 1); + } + }); + + // Get the products that have the tag + const productsWithTag = this._products.filter( + (product) => product.tags.indexOf(id) > -1 + ); + + // Iterate through them and delete the tag + productsWithTag.forEach((product) => { + product.tags.splice(product.tags.indexOf(id), 1); + }); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Vendors - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/ecommerce/inventory/vendors') + .reply(() => [200, cloneDeep(this._vendors)]); + } +} diff --git a/src/app/mock-api/apps/ecommerce/inventory/data.ts b/src/app/mock-api/apps/ecommerce/inventory/data.ts new file mode 100644 index 0000000..8a350f1 --- /dev/null +++ b/src/app/mock-api/apps/ecommerce/inventory/data.ts @@ -0,0 +1,845 @@ +/* eslint-disable */ +export const categories = [ + { + id: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + parentId: null, + name: 'Mens', + slug: 'mens', + }, + { + id: '07986d93-d4eb-4de1-9448-2538407f7254', + parentId: null, + name: 'Ladies', + slug: 'ladies', + }, + { + id: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + parentId: null, + name: 'Unisex', + slug: 'unisex', + }, +]; +export const brands = [ + { + id: 'e1789f32-9475-43e7-9256-451d2e3a2282', + name: 'Benton', + slug: 'benton', + }, + { + id: '61d52c2a-8947-4a2c-8c35-f36baef45b96', + name: 'Capmia', + slug: 'capmia', + }, + { + id: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + name: 'Lara', + slug: 'lara', + }, + { + id: '5913ee46-a497-41db-a118-ee506011529f', + name: 'Premera', + slug: 'premera', + }, + { + id: '2c4d98d8-f334-4125-9596-862515f5526b', + name: 'Zeon', + slug: 'zeon', + }, +]; +export const tags = [ + { + id: '167190fa-51b4-45fc-a742-8ce1b33d24ea', + title: 'mens', + }, + { + id: '3baea410-a7d6-4916-b79a-bdce50c37f95', + title: 'ladies', + }, + { + id: '8ec8f60d-552f-4216-9f11-462b95b1d306', + title: 'unisex', + }, + { + id: '8837b93f-388b-43cc-851d-4ca8f23f3a61', + title: '44mm', + }, + { + id: '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + title: '40mm', + }, + { + id: '2300ac48-f268-466a-b765-8b878b6e14a7', + title: '5 ATM', + }, + { + id: '0b11b742-3125-4d75-9a6f-84af7fde1969', + title: '10 ATM', + }, + { + id: '0fc39efd-f640-41f8-95a5-3f1d749df200', + title: 'automatic', + }, + { + id: '7d6dd47e-7472-4f8b-93d4-46c114c44533', + title: 'chronograph', + }, + { + id: 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + title: 'watch', + }, +]; +export const vendors = [ + { + id: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + name: 'Evel', + slug: 'evel', + }, + { + id: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + name: 'Mivon', + slug: 'mivon', + }, + { + id: '05ebb527-d733-46a9-acfb-a4e4ec960024', + name: 'Neogen', + slug: 'neogen', + }, +]; +export const products = [ + { + id: '7eb7c859-1347-4317-96b6-9476a7e2ba3c', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Capmia Mens Chronograph Watch 44mm 5 ATM', + description: + 'Consequat esse in culpa commodo anim. Et ullamco anim amet est. Sunt dolore ex occaecat officia anim. In sit minim laborum nostrud. Consequat ex do velit voluptate do exercitation est adipisicing quis velit.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ETV-2425', + barcode: '8346201275534', + brand: '61d52c2a-8947-4a2c-8c35-f36baef45b96', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 30, + reserved: 5, + cost: 450.18, + basePrice: 1036, + taxPercent: 30, + price: 1346.8, + weight: 0.61, + thumbnail: 'images/apps/ecommerce/products/watch-01-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-01-01.jpg', + 'images/apps/ecommerce/products/watch-01-02.jpg', + 'images/apps/ecommerce/products/watch-01-03.jpg', + ], + active: true, + }, + { + id: '00b0292f-3d50-4669-a0c4-7a9d85efc98d', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Zeon Ladies Chronograph Watch 40mm 10 ATM', + description: + 'Nulla duis dolor fugiat culpa proident. Duis anim est excepteur occaecat adipisicing occaecat. Labore id laborum non elit proident est veniam officia eu. Labore aliqua nisi duis sint ex consequat nostrud excepteur duis ex incididunt adipisicing.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATH-7573', + barcode: '8278968055700', + brand: '2c4d98d8-f334-4125-9596-862515f5526b', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 37, + reserved: 2, + cost: 723.55, + basePrice: 1686, + taxPercent: 30, + price: 2191.8, + weight: 0.79, + thumbnail: 'images/apps/ecommerce/products/watch-02-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-02-01.jpg', + 'images/apps/ecommerce/products/watch-02-02.jpg', + 'images/apps/ecommerce/products/watch-02-03.jpg', + ], + active: true, + }, + { + id: '3f34e2fb-95bf-4f61-be28-956d2c7e4eb2', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Benton Mens Automatic Watch 44mm 5 ATM', + description: + 'Velit irure deserunt aliqua officia. Eiusmod quis sunt magna laboris aliquip non dolor consequat cupidatat dolore esse. Consectetur mollit officia laborum fugiat nulla duis ad excepteur do aliqua fugiat. Fugiat non laboris exercitation ipsum in incididunt.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADH-1921', + barcode: '8808746892183', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 30, + reserved: 3, + cost: 390.63, + basePrice: 950, + taxPercent: 10, + price: 1045, + weight: 0.76, + thumbnail: null, + images: [ + 'images/apps/ecommerce/products/watch-03-01.jpg', + 'images/apps/ecommerce/products/watch-03-02.jpg', + 'images/apps/ecommerce/products/watch-03-03.jpg', + ], + active: false, + }, + { + id: '8fcce528-d878-4cc8-99f7-bd3451ed5405', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Capmia Mens Chronograph Watch 44mm 10 ATM', + description: + 'Velit nisi proident cupidatat exercitation occaecat et adipisicing nostrud id ex nostrud sint. Qui fugiat velit minim amet reprehenderit voluptate velit exercitation proident Lorem nisi culpa. Commodo quis officia officia eiusmod mollit aute fugiat duis quis minim culpa in. Exercitation laborum fugiat ex excepteur officia reprehenderit magna ipsum. Laboris dolore nostrud id labore sint consectetur aliqua tempor ea aute do.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'EAP-7752', + barcode: '8866355574164', + brand: '61d52c2a-8947-4a2c-8c35-f36baef45b96', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 37, + reserved: 4, + cost: 395.37, + basePrice: 839, + taxPercent: 30, + price: 1090.7, + weight: 0.62, + thumbnail: 'images/apps/ecommerce/products/watch-04-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-04-01.jpg', + 'images/apps/ecommerce/products/watch-04-02.jpg', + 'images/apps/ecommerce/products/watch-04-03.jpg', + ], + active: true, + }, + { + id: '91d96e18-d3f5-4c32-a8bf-1fc525cb92c0', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Benton Ladies Automatic Watch 40mm 5 ATM', + description: + 'Pariatur proident labore commodo consequat qui et. Ad labore fugiat consectetur ea magna dolore mollit consequat reprehenderit laborum ad mollit eiusmod. Esse laboris voluptate ullamco occaecat labore esse laboris enim ipsum aliquip ipsum. Ea ea proident eu enim anim mollit non consequat enim nulla.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADP-5745', + barcode: '8390590339828', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 12, + reserved: 3, + cost: 442.61, + basePrice: 961, + taxPercent: 20, + price: 1153.2, + weight: 0.67, + thumbnail: 'images/apps/ecommerce/products/watch-05-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-05-01.jpg', + 'images/apps/ecommerce/products/watch-05-02.jpg', + 'images/apps/ecommerce/products/watch-05-03.jpg', + ], + active: false, + }, + { + id: 'd7a47d7c-4cdf-4319-bbaa-37ade38c622c', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Benton Mens Chronograph Watch 44mm 10 ATM', + description: + 'Nulla enim reprehenderit proident ut Lorem laborum cillum eiusmod est ex anim. Nisi non non laboris excepteur ullamco elit do duis anim esse labore aliqua adipisicing velit. Deserunt magna exercitation cillum amet.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATV-2569', + barcode: '8238990048137', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 36, + reserved: 2, + cost: 563.43, + basePrice: 1370, + taxPercent: 30, + price: 1781, + weight: 0.62, + thumbnail: 'images/apps/ecommerce/products/watch-06-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-06-01.jpg', + 'images/apps/ecommerce/products/watch-06-02.jpg', + 'images/apps/ecommerce/products/watch-06-03.jpg', + ], + active: true, + }, + { + id: 'ecf0b3df-38c3-45dc-972b-c509a3dc053e', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Benton Mens Chronograph Watch 44mm 10 ATM', + description: + 'Esse culpa ut ullamco dolore quis adipisicing. Minim veniam quis magna officia non. In pariatur nostrud nisi eiusmod minim anim id. Commodo ex incididunt dolor ad id aliqua incididunt minim in Lorem reprehenderit. Commodo ullamco consectetur aliqua Lorem cupidatat esse veniam consectetur sint veniam duis commodo.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'EAH-2563', + barcode: '8638426908385', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 35, + reserved: 5, + cost: 705.26, + basePrice: 1721, + taxPercent: 20, + price: 2065.2, + weight: 0.67, + thumbnail: 'images/apps/ecommerce/products/watch-07-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-07-01.jpg', + 'images/apps/ecommerce/products/watch-07-02.jpg', + 'images/apps/ecommerce/products/watch-07-03.jpg', + ], + active: false, + }, + { + id: '5765080a-aaee-40b9-86be-c18b9d79c73c', + category: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + name: 'Benton Unisex Automatic Watch 40mm 10 ATM', + description: + 'Anim duis nisi ut ex amet reprehenderit cillum consequat pariatur ipsum elit voluptate excepteur non. Anim enim proident laboris pariatur mollit quis incididunt labore. Incididunt tempor aliquip ex labore ad consequat cillum est sunt anim dolor. Dolore adipisicing non nulla cillum Lorem deserunt. Nostrud incididunt amet sint velit.', + tags: [ + '8ec8f60d-552f-4216-9f11-462b95b1d306', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATH-6399', + barcode: '8881883828441', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 17, + reserved: 5, + cost: 624.12, + basePrice: 1448, + taxPercent: 10, + price: 1592.8, + weight: 0.55, + thumbnail: 'images/apps/ecommerce/products/watch-08-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-08-01.jpg', + 'images/apps/ecommerce/products/watch-08-02.jpg', + 'images/apps/ecommerce/products/watch-08-03.jpg', + ], + active: false, + }, + { + id: '6e71be88-b225-474c-91e5-111ced7d6220', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Premera Ladies Chronograph Watch 40mm 5 ATM', + description: + 'Velit fugiat adipisicing ut quis anim deserunt ex culpa nostrud laborum. Consectetur duis velit esse commodo voluptate magna dolor in enim exercitation. Ea aliquip cupidatat aute dolor tempor magna id laboris nulla eiusmod ut amet. Veniam irure ex incididunt officia commodo eiusmod nostrud ad consequat commodo ad voluptate.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ELH-2495', + barcode: '8268777127281', + brand: '5913ee46-a497-41db-a118-ee506011529f', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 49, + reserved: 5, + cost: 738.91, + basePrice: 1848, + taxPercent: 30, + price: 2402.4, + weight: 0.54, + thumbnail: 'images/apps/ecommerce/products/watch-09-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-09-01.jpg', + 'images/apps/ecommerce/products/watch-09-02.jpg', + 'images/apps/ecommerce/products/watch-09-03.jpg', + ], + active: false, + }, + { + id: '51242500-6983-4a78-bff3-d278eb4e3a57', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Lara Mens Automatic Watch 44mm 10 ATM', + description: + 'Enim laboris ut non elit dolore est consectetur. Duis irure minim elit velit anim incididunt minim ipsum ullamco ad dolore sunt. Proident aute proident velit elit ex reprehenderit ut. Lorem laborum excepteur elit proident sunt ipsum incididunt id do. Occaecat proident proident qui aute officia cupidatat aliqua aliqua nostrud proident laboris est ad qui. Magna eiusmod amet ut pariatur esse nisi aliquip deserunt minim ad et ea occaecat. Sunt enim cupidatat id eiusmod ea aute quis excepteur irure commodo dolore excepteur.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATT-6019', + barcode: '8452763551765', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 24, + reserved: 4, + cost: 688.89, + basePrice: 1502, + taxPercent: 8, + price: 1622.16, + weight: 0.76, + thumbnail: 'images/apps/ecommerce/products/watch-10-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-10-01.jpg', + 'images/apps/ecommerce/products/watch-10-02.jpg', + 'images/apps/ecommerce/products/watch-10-03.jpg', + ], + active: true, + }, + { + id: '844a4395-233f-4ffb-85bd-7baa0e490a88', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Lara Mens Chronograph Watch 44mm 5 ATM', + description: + 'Labore irure qui sunt consectetur. Elit nulla id cillum duis. Nulla nulla eu occaecat eiusmod duis irure id do esse. Ad eu incididunt voluptate amet nostrud ullamco mollit dolore occaecat cupidatat nisi reprehenderit. Proident fugiat laborum sit velit ea voluptate.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADH-2335', + barcode: '8385907318041', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 44, + reserved: 3, + cost: 708.41, + basePrice: 1467, + taxPercent: 18, + price: 1731.06, + weight: 0.7, + thumbnail: 'images/apps/ecommerce/products/watch-11-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-11-01.jpg', + 'images/apps/ecommerce/products/watch-11-02.jpg', + 'images/apps/ecommerce/products/watch-11-03.jpg', + ], + active: false, + }, + { + id: '7520f1b6-3c45-46ef-a4d5-881971212d1e', + category: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + name: 'Benton Unisex Automatic Watch 40mm 10 ATM', + description: + 'Esse nisi amet occaecat culpa aliqua est ad ea velit. Consectetur in voluptate sit pariatur eiusmod exercitation eu aute occaecat in duis. Voluptate consectetur eu commodo proident id sunt labore irure.', + tags: [ + '8ec8f60d-552f-4216-9f11-462b95b1d306', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATH-3064', + barcode: '8608510561856', + brand: 'e1789f32-9475-43e7-9256-451d2e3a2282', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 25, + reserved: 2, + cost: 731.94, + basePrice: 1743, + taxPercent: 10, + price: 1917.3, + weight: 0.47, + thumbnail: 'images/apps/ecommerce/products/watch-12-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-12-01.jpg', + 'images/apps/ecommerce/products/watch-12-02.jpg', + 'images/apps/ecommerce/products/watch-12-03.jpg', + ], + active: false, + }, + { + id: '683e41d8-6ebc-4e6a-a7c1-9189ca52ef19', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Zeon Mens Chronograph Watch 44mm 10 ATM', + description: + 'Eu irure do cupidatat esse in. Aliqua laborum deserunt qui Lorem deserunt minim fugiat deserunt voluptate minim. Anim nulla tempor eiusmod ad exercitation reprehenderit officia. Nisi proident labore eu anim excepteur aliqua occaecat. Laboris nostrud ipsum commodo cupidatat.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADV-3188', + barcode: '8334758988643', + brand: '2c4d98d8-f334-4125-9596-862515f5526b', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 14, + reserved: 5, + cost: 375.76, + basePrice: 786, + taxPercent: 30, + price: 1021.8, + weight: 0.53, + thumbnail: 'images/apps/ecommerce/products/watch-13-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-13-01.jpg', + 'images/apps/ecommerce/products/watch-13-02.jpg', + 'images/apps/ecommerce/products/watch-13-03.jpg', + ], + active: false, + }, + { + id: 'd4e52238-292d-462b-b9bb-1751030132e2', + category: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + name: 'Lara Unisex Chronograph Watch 40mm 5 ATM', + description: + 'Nulla nostrud aliquip consequat laborum ut enim exercitation. Aute dolor duis aliquip consequat minim officia. Nisi labore et magna et sunt consectetur id anim pariatur officia et esse ut. Ullamco dolor cillum consequat velit eiusmod consectetur. Ullamco reprehenderit tempor minim dolore officia do nisi cupidatat adipisicing fugiat velit.', + tags: [ + '8ec8f60d-552f-4216-9f11-462b95b1d306', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATT-7423', + barcode: '8417153336369', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 33, + reserved: 2, + cost: 743.93, + basePrice: 1793, + taxPercent: 8, + price: 1936.44, + weight: 0.86, + thumbnail: 'images/apps/ecommerce/products/watch-14-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-14-01.jpg', + 'images/apps/ecommerce/products/watch-14-02.jpg', + 'images/apps/ecommerce/products/watch-14-03.jpg', + ], + active: false, + }, + { + id: '98861dfc-0d21-4fd5-81aa-49785d003d95', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Premera Mens Automatic Watch 44mm 10 ATM', + description: + 'Veniam sint aliquip aliquip aliquip amet Lorem irure proident laborum et eiusmod aliqua. Aliquip deserunt voluptate magna ut quis magna dolor in dolore. Commodo adipisicing excepteur occaecat aute nisi in. Est aute ad ut incididunt anim ea commodo. Sunt excepteur duis sunt est laborum magna Lorem ullamco exercitation dolore irure.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'AAT-6453', + barcode: '8501386761670', + brand: '5913ee46-a497-41db-a118-ee506011529f', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 38, + reserved: 3, + cost: 364.64, + basePrice: 806, + taxPercent: 18, + price: 951.08, + weight: 0.59, + thumbnail: 'images/apps/ecommerce/products/watch-15-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-15-01.jpg', + 'images/apps/ecommerce/products/watch-15-02.jpg', + 'images/apps/ecommerce/products/watch-15-03.jpg', + ], + active: false, + }, + { + id: 'a71f9b10-e884-4aad-9810-29fe10ce6d42', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Lara Ladies Chronograph Watch 40mm 5 ATM', + description: + 'Deserunt non deserunt ut do labore cupidatat duis veniam in non adipisicing officia esse id. Adipisicing Lorem sint excepteur culpa labore consequat incididunt nulla minim amet. Sint do et fugiat laborum exercitation reprehenderit ut non nostrud occaecat nisi et qui dolore. Amet eiusmod nulla est officia ad magna cillum non dolor ullamco officia incididunt.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'AAP-4902', + barcode: '8847387136582', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 40, + reserved: 3, + cost: 525.3, + basePrice: 1303, + taxPercent: 10, + price: 1433.3, + weight: 0.69, + thumbnail: 'images/apps/ecommerce/products/watch-16-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-16-01.jpg', + 'images/apps/ecommerce/products/watch-16-02.jpg', + 'images/apps/ecommerce/products/watch-16-03.jpg', + ], + active: false, + }, + { + id: '149e6db5-4ecc-4021-bc56-08b27514a746', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Lara Ladies Chronograph Watch 40mm 5 ATM', + description: + 'Occaecat proident fugiat consectetur ullamco est. Duis non minim eiusmod magna dolor reprehenderit ad deserunt et qui amet. Tempor cillum dolore veniam Lorem sit ad pariatur et sint. Sunt anim et cupidatat Lorem proident fugiat incididunt incididunt minim non sint. Eiusmod quis et ullamco cillum et veniam do tempor officia sint.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '2300ac48-f268-466a-b765-8b878b6e14a7', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ALV-194', + barcode: '8860845382207', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 20, + reserved: 2, + cost: 670.87, + basePrice: 1537, + taxPercent: 8, + price: 1659.96, + weight: 0.66, + thumbnail: 'images/apps/ecommerce/products/watch-17-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-17-01.jpg', + 'images/apps/ecommerce/products/watch-17-02.jpg', + 'images/apps/ecommerce/products/watch-17-03.jpg', + ], + active: false, + }, + { + id: '655287de-2e24-41f3-a82f-8b08548ecc39', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Zeon Mens Automatic Watch 44mm 10 ATM', + description: + 'Eiusmod magna tempor est est quis eu. Minim irure magna anim mollit non adipisicing aute. Nostrud aute consectetur eu in non laboris excepteur esse esse occaecat officia.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADH-5492', + barcode: '8611606513571', + brand: '2c4d98d8-f334-4125-9596-862515f5526b', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 47, + reserved: 2, + cost: 645.13, + basePrice: 1581, + taxPercent: 10, + price: 1739.1, + weight: 0.54, + thumbnail: 'images/apps/ecommerce/products/watch-18-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-18-01.jpg', + 'images/apps/ecommerce/products/watch-18-02.jpg', + 'images/apps/ecommerce/products/watch-18-03.jpg', + ], + active: true, + }, + { + id: 'c215b427-d840-4537-aea1-a9bdfa49441b', + category: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + name: 'Lara Unisex Automatic Watch 40mm 10 ATM', + description: + 'Excepteur enim non qui consequat sunt exercitation laborum ipsum sunt. Sunt pariatur fugiat voluptate ipsum consectetur do magna culpa labore. Cupidatat non ex labore incididunt aliquip commodo est in. Consectetur mollit nisi aliquip cupidatat do laborum est ullamco velit aliqua fugiat qui adipisicing. Aute reprehenderit quis id sint nulla.', + tags: [ + '8ec8f60d-552f-4216-9f11-462b95b1d306', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'AAT-6702', + barcode: '8330223562386', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 21, + reserved: 3, + cost: 704.26, + basePrice: 1733, + taxPercent: 10, + price: 1906.3, + weight: 0.84, + thumbnail: 'images/apps/ecommerce/products/watch-19-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-19-01.jpg', + 'images/apps/ecommerce/products/watch-19-02.jpg', + 'images/apps/ecommerce/products/watch-19-03.jpg', + ], + active: true, + }, + { + id: '8b1d9366-891e-49cd-aafb-ac65ce2741e2', + category: '07986d93-d4eb-4de1-9448-2538407f7254', + name: 'Zeon Ladies Automatic Watch 40mm 10 ATM', + description: + 'Reprehenderit magna reprehenderit ex mollit Lorem labore ut. Duis consectetur aliqua cillum occaecat quis ex excepteur fugiat nulla nisi dolor minim. Elit voluptate exercitation nulla et ut adipisicing esse eu nisi amet eu. Ut cillum ipsum quis fugiat proident Lorem est aute ipsum sint dolore consequat.', + tags: [ + '3baea410-a7d6-4916-b79a-bdce50c37f95', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'EDH-5599', + barcode: '8309212335274', + brand: '2c4d98d8-f334-4125-9596-862515f5526b', + vendor: '05ebb527-d733-46a9-acfb-a4e4ec960024', + stock: 35, + reserved: 2, + cost: 712.66, + basePrice: 1711, + taxPercent: 30, + price: 2224.3, + weight: 0.47, + thumbnail: 'images/apps/ecommerce/products/watch-20-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-20-01.jpg', + 'images/apps/ecommerce/products/watch-20-02.jpg', + 'images/apps/ecommerce/products/watch-20-03.jpg', + ], + active: false, + }, + { + id: '54e29534-518b-4006-b72a-f21fac6c4d5e', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Lara Mens Chronograph Watch 44mm 10 ATM', + description: + 'Officia eu magna eu amet fugiat qui ullamco eu. Occaecat dolore minim ad tempor consequat adipisicing non Lorem consequat. In nostrud incididunt adipisicing in. Irure occaecat aliquip deserunt minim officia ad excepteur do commodo magna.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ADP-3719', + barcode: '8879167838673', + brand: 'f9987124-7ada-4b93-bef7-35280b3ddbd7', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 28, + reserved: 3, + cost: 374.38, + basePrice: 749, + taxPercent: 8, + price: 808.92, + weight: 0.52, + thumbnail: 'images/apps/ecommerce/products/watch-21-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-21-01.jpg', + 'images/apps/ecommerce/products/watch-21-02.jpg', + 'images/apps/ecommerce/products/watch-21-03.jpg', + ], + active: false, + }, + { + id: '6a5726e8-c467-45ea-92ab-d83235a06405', + category: 'b899ec30-b85a-40ab-bb1f-18a596d5c6de', + name: 'Premera Mens Chronograph Watch 44mm 10 ATM', + description: + 'Duis id consequat ex officia nisi. Et reprehenderit tempor sunt nostrud. Duis dolore tempor anim non duis qui aute magna officia. Ullamco proident esse enim amet nostrud occaecat veniam. Nostrud ea eiusmod laborum id laborum veniam nulla. Voluptate proident ullamco exercitation id consequat dolore id pariatur esse nulla consectetur.', + tags: [ + '167190fa-51b4-45fc-a742-8ce1b33d24ea', + '7d6dd47e-7472-4f8b-93d4-46c114c44533', + '8837b93f-388b-43cc-851d-4ca8f23f3a61', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'ATH-3399', + barcode: '8356410903599', + brand: '5913ee46-a497-41db-a118-ee506011529f', + vendor: '987dd10a-43b1-49f9-bfd9-05bb2dbc7029', + stock: 20, + reserved: 2, + cost: 444.68, + basePrice: 1103, + taxPercent: 18, + price: 1301.54, + weight: 0.56, + thumbnail: 'images/apps/ecommerce/products/watch-22-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-22-01.jpg', + 'images/apps/ecommerce/products/watch-22-02.jpg', + 'images/apps/ecommerce/products/watch-22-03.jpg', + ], + active: false, + }, + { + id: 'd7d1d6df-e91f-4c53-982a-2720bc2b4cdd', + category: 'ad12aa94-3863-47f8-acab-a638ef02a3e9', + name: 'Capmia Unisex Automatic Watch 40mm 10 ATM', + description: + 'Voluptate consectetur nisi aliquip cupidatat sunt labore. Adipisicing voluptate tempor sunt eu irure cupidatat laboris. Enim aliquip aute sit non laborum Lorem in enim duis eu deserunt. Laboris magna irure aute ut proident fugiat laborum aliquip tempor nostrud id. Et esse cupidatat sunt ullamco reprehenderit enim dolore ea in do esse esse id.', + tags: [ + '8ec8f60d-552f-4216-9f11-462b95b1d306', + '0fc39efd-f640-41f8-95a5-3f1d749df200', + '8f868ddb-d4a2-461d-bc3b-d7c8668687c3', + '0b11b742-3125-4d75-9a6f-84af7fde1969', + 'b1286f3a-e2d0-4237-882b-f0efc0819ec3', + ], + sku: 'EAV-4030', + barcode: '8545771786193', + brand: '61d52c2a-8947-4a2c-8c35-f36baef45b96', + vendor: '998b0c07-abfd-4ba3-8de1-7563ef3c4d57', + stock: 23, + reserved: 3, + cost: 538.72, + basePrice: 1213, + taxPercent: 10, + price: 1334.3, + weight: 0.75, + thumbnail: 'images/apps/ecommerce/products/watch-23-thumb.jpg', + images: [ + 'images/apps/ecommerce/products/watch-23-01.jpg', + 'images/apps/ecommerce/products/watch-23-02.jpg', + 'images/apps/ecommerce/products/watch-23-03.jpg', + ], + active: true, + }, +]; diff --git a/src/app/mock-api/apps/file-manager/api.ts b/src/app/mock-api/apps/file-manager/api.ts new file mode 100644 index 0000000..f2993e6 --- /dev/null +++ b/src/app/mock-api/apps/file-manager/api.ts @@ -0,0 +1,91 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; +import { items as itemsData } from 'app/mock-api/apps/file-manager/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class FileManagerMockApi { + private _items: any[] = itemsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Items - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/file-manager') + .reply(({ request }) => { + // Clone the items + let items = cloneDeep(this._items); + + // See if the folder id exist + const folderId = + request.params.get('folderId') === 'null' + ? null + : request.params.get('folderId'); + + // Filter the items by folder id. If folder id is null, + // that means we want to root items which have folder id + // of null + items = items.filter((item) => item.folderId === folderId); + + // Separate the items by folders and files + const folders = items.filter((item) => item.type === 'folder'); + const files = items.filter((item) => item.type !== 'folder'); + + // Sort the folders and files alphabetically by filename + folders.sort((a, b) => a.name.localeCompare(b.name)); + files.sort((a, b) => a.name.localeCompare(b.name)); + + // Figure out the path and attach it to the response + // Prepare the empty paths array + const pathItems = cloneDeep(this._items); + const path = []; + + // Prepare the current folder + let currentFolder = null; + + // Get the current folder and add it as the first entry + if (folderId) { + currentFolder = pathItems.find( + (item) => item.id === folderId + ); + path.push(currentFolder); + } + + // Start traversing and storing the folders as a path array + // until we hit null on the folder id + while (currentFolder?.folderId) { + currentFolder = pathItems.find( + (item) => item.id === currentFolder.folderId + ); + if (currentFolder) { + path.unshift(currentFolder); + } + } + + return [ + 200, + { + folders, + files, + path, + }, + ]; + }); + } +} diff --git a/src/app/mock-api/apps/file-manager/data.ts b/src/app/mock-api/apps/file-manager/data.ts new file mode 100644 index 0000000..1107e58 --- /dev/null +++ b/src/app/mock-api/apps/file-manager/data.ts @@ -0,0 +1,258 @@ +/* eslint-disable */ +export const items = [ + { + id: 'cd6897cb-acfd-4016-8b53-3f66a5b5fc68', + folderId: null, + name: 'Personal', + createdBy: 'Username', + createdAt: 'April 24, 2018', + modifiedAt: 'April 24, 2018', + size: '87 MB', + type: 'folder', + contents: '57 files', + description: + 'Personal documents such as insurance policies, tax papers and etc.', + }, + { + id: '6da8747f-b474-4c9a-9eba-5ef212285500', + folderId: null, + name: 'Photos', + createdBy: 'Username', + createdAt: 'November 01, 2021', + modifiedAt: 'November 01, 2021', + size: '3015 MB', + type: 'folder', + contents: '907 files', + description: 'Personal photos; selfies, family, vacation and etc.', + }, + { + id: 'ed58add1-45a7-41db-887d-3ca7ee7f2719', + folderId: null, + name: 'Work', + createdBy: 'Username', + createdAt: 'May 8, 2020', + modifiedAt: 'May 8, 2020', + size: '14 MB', + type: 'folder', + contents: '24 files', + description: 'Work related files, mainly documents and paychecks.', + }, + { + id: '5cb66e32-d1ac-4b9a-8c34-5991ce25add2', + folderId: null, + name: 'Contract #123', + createdBy: 'Username', + createdAt: 'January 14, 2021', + modifiedAt: 'January 14, 2021', + size: '1.2 MB', + type: 'PDF', + contents: null, + description: null, + }, + { + id: '3ffc3d84-8f2d-4929-903a-ef6fc21657a7', + folderId: null, + name: 'Estimated budget', + createdBy: 'Username', + createdAt: 'December 14, 2020', + modifiedAt: 'December 14, 2020', + size: '679 KB', + type: 'XLS', + contents: null, + description: null, + }, + { + id: '157adb9a-14f8-4559-ac93-8be893c9f80a', + folderId: null, + name: 'DMCA notice #42', + createdBy: 'Username', + createdAt: 'May 8, 2021', + modifiedAt: 'May 8, 2021', + size: '1.5 MB', + type: 'DOC', + contents: null, + description: null, + }, + { + id: '4f64597a-df7e-461c-ad60-f33e5f7e0747', + folderId: null, + name: 'Invoices', + createdBy: 'Username', + createdAt: 'January 12, 2020', + modifiedAt: 'January 12, 2020', + size: '17.8 MB', + type: 'PDF', + contents: null, + description: null, + }, + { + id: 'e445c445-57b2-4476-8c62-b068e3774b8e', + folderId: null, + name: 'Crash logs', + createdBy: 'Username', + createdAt: 'June 8, 2020', + modifiedAt: 'June 8, 2020', + size: '11.3 MB', + type: 'TXT', + contents: null, + description: null, + }, + { + id: 'b482f93e-7847-4614-ad48-b78b78309f81', + folderId: null, + name: 'System logs', + createdBy: 'Username', + createdAt: 'June 8, 2020', + modifiedAt: 'June 8, 2020', + size: '9.3 MB', + type: 'TXT', + contents: null, + description: null, + }, + { + id: 'ec07a98d-2e5b-422c-a9b2-b5d1c0e263f5', + folderId: null, + name: 'Personal projects', + createdBy: 'Username', + createdAt: 'March 18, 2020', + modifiedAt: 'March 18, 2020', + size: '4.3 MB', + type: 'DOC', + contents: null, + description: null, + }, + { + id: 'ae908d59-07da-4dd8-aba0-124e50289295', + folderId: null, + name: 'Biometric portrait', + createdBy: 'Username', + createdAt: 'August 29, 2020', + modifiedAt: 'August 29, 2020', + size: '4.5 MB', + type: 'JPG', + contents: null, + description: null, + }, + { + id: '4038a5b6-5b1a-432d-907c-e037aeb817a8', + folderId: null, + name: 'Scanned image 20201012-1', + createdBy: 'Username', + createdAt: 'September 13, 2020', + modifiedAt: 'September 13, 2020', + size: '7.8 MB', + type: 'JPG', + contents: null, + description: null, + }, + { + id: '630d2e9a-d110-47a0-ac03-256073a0f56d', + folderId: null, + name: 'Scanned image 20201012-2', + createdBy: 'Username', + createdAt: 'September 14, 2020', + modifiedAt: 'September 14, 2020', + size: '7.4 MB', + type: 'JPG', + contents: null, + description: null, + }, + { + id: '1417d5ed-b616-4cff-bfab-286677b69d79', + folderId: null, + name: 'Prices', + createdBy: 'Username', + createdAt: 'April 07, 2020', + modifiedAt: 'April 07, 2020', + size: '2.6 MB', + type: 'DOC', + contents: null, + description: null, + }, + { + id: 'bd2817c7-6751-40dc-b252-b6b5634c0689', + folderId: null, + name: 'Shopping list', + createdBy: 'Username', + createdAt: 'March 26, 2021', + modifiedAt: 'March 26, 2021', + size: '2.1 MB', + type: 'DOC', + contents: null, + description: null, + }, + { + id: '14fb47c9-6eeb-4070-919c-07c8133285d1', + folderId: null, + name: 'Summer budget', + createdBy: 'Username', + createdAt: 'June 02, 2020', + modifiedAt: 'June 02, 2020', + size: '943 KB', + type: 'XLS', + contents: null, + description: null, + }, + + { + id: '894e8514-03d3-4f5e-bb28-f6c092501fae', + folderId: 'cd6897cb-acfd-4016-8b53-3f66a5b5fc68', + name: 'A personal file', + createdBy: 'Username', + createdAt: 'June 02, 2020', + modifiedAt: 'June 02, 2020', + size: '943 KB', + type: 'XLS', + contents: null, + description: null, + }, + { + id: '74010810-16cf-441d-a1aa-c9fb620fceea', + folderId: 'cd6897cb-acfd-4016-8b53-3f66a5b5fc68', + name: 'A personal folder', + createdBy: 'Username', + createdAt: 'November 01, 2021', + modifiedAt: 'November 01, 2021', + size: '3015 MB', + type: 'folder', + contents: '907 files', + description: 'Personal photos; selfies, family, vacation and etc.', + }, + { + id: 'a8c73e5a-8114-436d-ab54-d900b50b3762', + folderId: '74010810-16cf-441d-a1aa-c9fb620fceea', + name: 'A personal file within the personal folder', + createdBy: 'Username', + createdAt: 'June 02, 2020', + modifiedAt: 'June 02, 2020', + size: '943 KB', + type: 'XLS', + contents: null, + description: null, + }, + + { + id: '12d851a8-4f60-473e-8a59-abe4b422ea99', + folderId: '6da8747f-b474-4c9a-9eba-5ef212285500', + name: 'Photos file', + createdBy: 'Username', + createdAt: 'June 02, 2020', + modifiedAt: 'June 02, 2020', + size: '943 KB', + type: 'XLS', + contents: null, + description: null, + }, + { + id: '2836766d-27e1-4f40-a31a-5a8419105e7e', + folderId: 'ed58add1-45a7-41db-887d-3ca7ee7f2719', + name: 'Work file', + createdBy: 'Username', + createdAt: 'June 02, 2020', + modifiedAt: 'June 02, 2020', + size: '943 KB', + type: 'XLS', + contents: null, + description: null, + }, +]; diff --git a/src/app/mock-api/apps/help-center/api.ts b/src/app/mock-api/apps/help-center/api.ts new file mode 100644 index 0000000..8a91b0c --- /dev/null +++ b/src/app/mock-api/apps/help-center/api.ts @@ -0,0 +1,175 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { + faqCategories as faqCategoriesData, + faqs as faqsData, + guideCategories as guideCategoriesData, + guideContent as guideContentData, + guides as guidesData, +} from 'app/mock-api/apps/help-center/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class HelpCenterMockApi { + private _faqCategories: any[] = faqCategoriesData; + private _faqs: any[] = faqsData; + private _guideCategories: any[] = guideCategoriesData; + private _guides: any[] = guidesData; + private _guideContent: string = guideContentData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ FAQs - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/help-center/faqs') + .reply(({ request }) => { + // Get the category slug + const slug = request.params.get('slug'); + + // Prepare the results + const results = []; + + // Get FAQs + const faqs = cloneDeep(this._faqs); + + // Get FAQ Categories + const categories = cloneDeep(this._faqCategories); + + // If slug is not provided... + if (!slug) { + // Go through each category and set the results + categories.forEach((category) => { + results.push({ + ...category, + faqs: faqs.filter( + (faq) => faq.categoryId === category.id + ), + }); + }); + } + // Otherwise... + else { + // Find the category by the slug + const category = categories.find( + (item) => item.slug === slug + ); + + // Set the results + results.push({ + ...category, + faqs: faqs.filter( + (faq) => faq.categoryId === category.id + ), + }); + } + + // Return the response + return [200, results]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Guides - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/help-center/guides') + .reply(({ request }) => { + // Get the slug & limit + const slug = request.params.get('slug'); + const limit = request.params.get('limit'); + + // Prepare the results + const results = []; + + // Get all Guides + const guides = cloneDeep(this._guides); + + // Get Guide categories + const categories = cloneDeep(this._guideCategories); + + // If slug is not provided... + if (!slug) { + // Parse the limit as an integer + const limitNum = parseInt(limit ?? '5', 10); + + // Go through each category and set the results + categories.forEach((category) => { + results.push({ + ...category, + visibleGuides: limitNum, + totalGuides: guides.filter( + (guide) => guide.categoryId === category.id + ).length, + guides: guides + .filter( + (guide) => guide.categoryId === category.id + ) + .slice(0, limitNum), + }); + }); + } + // Otherwise... + else { + // Find the category by the slug + const category = categories.find( + (item) => item.slug === slug + ); + + // Set the results + results.push({ + ...category, + guides: guides.filter( + (guide) => guide.categoryId === category.id + ), + }); + } + + // Return the response + return [200, results]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Guide - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/help-center/guide') + .reply(({ request }) => { + // Get the slugs + const categorySlug = request.params.get('categorySlug'); + const guideSlug = request.params.get('guideSlug'); + + // Get all Guides and Guide Categories + const guides = cloneDeep(this._guides); + const categories = cloneDeep(this._guideCategories); + + // Prepare the result + const result = { + ...categories.find( + (category) => category.slug === categorySlug + ), + guides: [guides.find((guide) => guide.slug === guideSlug)], + }; + + // Add the content to the guide + result.guides[0]['content'] = this._guideContent; + + // Return the response + return [200, result]; + }); + } +} diff --git a/src/app/mock-api/apps/help-center/data.ts b/src/app/mock-api/apps/help-center/data.ts new file mode 100644 index 0000000..355b904 --- /dev/null +++ b/src/app/mock-api/apps/help-center/data.ts @@ -0,0 +1,534 @@ +/* eslint-disable */ +export const faqCategories = [ + { + id: '28924eab-97cc-465a-ba21-f232bb95843f', + slug: 'most-asked', + title: 'Most asked', + }, + { + id: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + slug: 'general-inquiries', + title: 'General inquiries', + }, + { + id: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + slug: 'licenses', + title: 'Licenses', + }, + { + id: '71c34043-d89d-4aca-951d-8606c3943c43', + slug: 'payments', + title: 'Payments', + }, + { + id: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + slug: 'support', + title: 'Support', + }, +]; +export const faqs = [ + // Most asked + { + id: 'f65d517a-6f69-4c88-81f5-416f47405ce1', + categoryId: '28924eab-97cc-465a-ba21-f232bb95843f', + question: 'Is there a 14-days trial?', + answer: 'Magna consectetur culpa duis ad est tempor pariatur velit ullamco aute exercitation magna sunt commodo minim enim aliquip eiusmod ipsum adipisicing magna ipsum reprehenderit lorem magna voluptate magna aliqua culpa.\n\nSit nisi adipisicing pariatur enim enim sunt officia ad labore voluptate magna proident velit excepteur pariatur cillum sit excepteur elit veniam excepteur minim nisi cupidatat proident dolore irure veniam mollit.', + }, + { + id: '0fcece82-1691-4b98-a9b9-b63218f9deef', + categoryId: '28924eab-97cc-465a-ba21-f232bb95843f', + question: 'What’s the benefits of the Premium Membership?', + answer: 'Et in lorem qui ipsum deserunt duis exercitation lorem elit qui qui ipsum tempor nulla velit aliquip enim consequat incididunt pariatur duis excepteur elit irure nulla ipsum dolor dolore est.\n\nAute deserunt nostrud id non ipsum do adipisicing laboris in minim officia magna elit minim mollit elit velit veniam lorem pariatur veniam sit excepteur irure commodo excepteur duis quis in.', + }, + { + id: '2e6971cd-49d5-49f1-8cbd-fba5c71e6062', + categoryId: '28924eab-97cc-465a-ba21-f232bb95843f', + question: 'How much time I will need to learn this app?', + answer: 'Id fugiat et cupidatat magna nulla nulla eu cillum officia nostrud dolore in veniam ullamco nulla ex duis est enim nisi aute ipsum velit et laboris est pariatur est culpa.\n\nCulpa sunt ipsum esse quis excepteur enim culpa est voluptate reprehenderit consequat duis officia irure voluptate veniam dolore fugiat dolor est amet nostrud non velit irure do voluptate id sit.', + }, + { + id: '974f93b8-336f-4eec-b011-9ddb412ee828', + categoryId: '28924eab-97cc-465a-ba21-f232bb95843f', + question: 'Are there any free tutorials available?', + answer: 'Excepteur deserunt tempor do lorem elit id magna pariatur irure ullamco elit dolor consectetur ad officia fugiat incididunt do elit aute esse eu voluptate adipisicing incididunt ea dolor aliqua dolor.\n\nConsequat est quis deserunt voluptate ipsum incididunt laboris occaecat irure laborum voluptate non sit labore voluptate sunt id sint ut laboris aute cupidatat occaecat eiusmod non magna aliquip deserunt nisi.', + }, + { + id: '5d877fc7-b881-4527-a6aa-d39d642feb23', + categoryId: '28924eab-97cc-465a-ba21-f232bb95843f', + question: 'Is there a month-to-month payment option?', + answer: 'Labore mollit in aliqua exercitation aliquip elit nisi nisi voluptate reprehenderit et dolor incididunt cupidatat ullamco nulla consequat voluptate adipisicing dolor qui magna sint aute do excepteur in aliqua consectetur.\n\nElit laborum non duis irure ad ullamco aliqua enim exercitation quis fugiat aute esse esse magna et ad cupidatat voluptate sint nulla nulla lorem et enim deserunt proident deserunt consectetur.', + }, + // General inquiries + { + id: '3d1c26c5-1e5e-4eb6-8006-ed6037ed9aca', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: 'How to download your items', + answer: 'Sunt mollit irure dolor aliquip sit veniam amet ut sunt dolore cillum sint pariatur qui irure proident velit non excepteur quis ut et quis velit aliqua ea sunt cillum sit.\n\nReprehenderit est culpa ut incididunt sit dolore mollit in occaecat velit culpa consequat reprehenderit ex lorem cupidatat proident reprehenderit ad eu sunt sit ut sit culpa ea reprehenderit aliquip est.', + }, + { + id: '11bd2b9a-85b4-41c9-832c-bd600dfa3a52', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: 'View and download invoices', + answer: 'Sint mollit consectetur voluptate fugiat sunt ipsum adipisicing labore exercitation eiusmod enim excepteur enim proident velit sint magna commodo dolor ex ipsum sit nisi deserunt labore eu irure amet ea.\n\nOccaecat ut velit et sint pariatur laboris voluptate duis aliqua aliqua exercitation et duis duis eu laboris excepteur occaecat quis esse enim ex dolore commodo fugiat excepteur adipisicing in fugiat.', + }, + { + id: 'f55c023a-785e-4f0f-b5b7-47da75224deb', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: "I've forgotten my username or password", + answer: 'In exercitation sunt ad anim commodo sunt do in sunt est officia amet ex ullamco do nisi consectetur lorem proident lorem adipisicing incididunt consequat fugiat voluptate sint est anim officia.\n\nVelit sint aliquip elit culpa amet eu mollit veniam esse deserunt ex occaecat quis lorem minim occaecat culpa esse veniam enim duis excepteur ipsum esse ut ut velit cillum adipisicing.', + }, + { + id: 'c577a67d-357a-4b88-96e8-a0ee1fe9162e', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: 'Where is my license code?', + answer: 'Ad adipisicing duis consequat magna sunt consequat aliqua eiusmod qui et nostrud voluptate sit enim reprehenderit anim exercitation ipsum ipsum anim ipsum laboris aliqua ex lorem aute officia voluptate culpa.\n\nNostrud anim ex pariatur ipsum et nostrud esse veniam ipsum ipsum irure velit ad quis irure tempor nulla amet aute id esse reprehenderit ea consequat consequat ea minim magna magna.', + }, + { + id: '1a680c29-7ece-4a80-9709-277ad4da8b4b', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: 'How to contact an author', + answer: 'Magna laborum et amet magna fugiat officia deserunt in exercitation aliquip nulla magna velit ea labore quis deserunt ipsum occaecat id id consequat non eiusmod mollit est voluptate ea ex.\n\nReprehenderit mollit ut excepteur minim veniam fugiat enim id pariatur amet elit nostrud occaecat pariatur et esse aliquip irure quis officia reprehenderit voluptate voluptate est et voluptate sint esse dolor.', + }, + { + id: 'c49c2216-8bdb-4df0-be25-d5ea1dbb5688', + categoryId: '395b0d41-b9a8-4cd6-8b5c-f07855e82d62', + question: 'How does the affiliate program work?', + answer: 'Adipisicing laboris ipsum fugiat et cupidatat aute esse ad labore et est cillum ipsum sunt duis do veniam minim officia deserunt in eiusmod eu duis dolore excepteur consectetur id elit.\n\nAnim excepteur occaecat laborum sunt in elit quis sit duis adipisicing laboris anim laborum et pariatur elit qui consectetur laborum reprehenderit occaecat nostrud pariatur aliqua elit nisi commodo eu excepteur.', + }, + // Licenses + { + id: '3ef176fa-6cba-4536-9f43-540c686a4faa', + categoryId: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + question: 'How do licenses work for items I bought?', + answer: 'Culpa duis nostrud qui velit sint magna officia fugiat ipsum eiusmod enim laborum pariatur anim culpa elit ipsum lorem pariatur exercitation laborum do labore cillum exercitation nisi reprehenderit exercitation quis.\n\nMollit aute dolor non elit et incididunt eiusmod non in commodo occaecat id in excepteur aliqua ea anim pariatur sint elit voluptate dolor eu non laborum laboris voluptate qui duis.', + }, + { + id: '7bc6b7b4-7ad8-4cbe-af36-7301642d35fb', + categoryId: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + question: 'Do licenses have an expiry date?', + answer: 'Ea proident dolor tempor dolore incididunt velit incididunt ullamco quis proident consectetur magna excepteur cillum officia ex do aliqua reprehenderit est esse officia labore dolore aute laboris eu commodo aute.\n\nOfficia quis id ipsum adipisicing ipsum eu exercitation cillum ex elit pariatur adipisicing ullamco ullamco nulla dolore magna aliqua reprehenderit eu laborum voluptate reprehenderit non eiusmod deserunt velit magna do.', + }, + { + id: '56c9ed66-a1d2-4803-a160-fba29b826cb4', + categoryId: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + question: 'I want to make multiple end products with the same item', + answer: 'Elit cillum incididunt enim cupidatat ex elit cillum aute dolor consectetur proident non minim eu est deserunt proident mollit ullamco laborum anim ea labore anim ex enim ullamco consectetur enim.\n\nEx magna consectetur esse enim consequat non aliqua nulla labore mollit sit quis ex fugiat commodo eu cupidatat irure incididunt consequat enim ut deserunt consequat elit consequat sint adipisicing sunt.', + }, + { + id: '21c1b662-33c8-44d7-9530-91896afeeac7', + categoryId: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + question: 'How easy is it to change the license type?', + answer: 'Duis culpa ut veniam voluptate consequat proident magna eiusmod id est magna culpa nulla enim culpa mollit velit lorem mollit ut minim dolore in tempor reprehenderit cillum occaecat proident ea.\n\nVeniam fugiat ea duis qui et eu eiusmod voluptate id cillum eiusmod eu reprehenderit minim reprehenderit nisi cillum nostrud duis eu magna minim sunt voluptate eu pariatur nulla ullamco elit.', + }, + { + id: '5fa52c90-82be-41ae-96ec-5fc67cf054a4', + categoryId: 'b388a87f-bfbb-44d0-800c-0ddbce2a5d22', + question: 'Do I need a Regular License or an Extended License?', + answer: 'Mollit nostrud ea irure ex ipsum in cupidatat irure sit officia reprehenderit adipisicing et occaecat cupidatat exercitation mollit esse in excepteur qui elit exercitation velit fugiat exercitation est officia excepteur.\n\nQuis esse voluptate laborum non veniam duis est fugiat tempor culpa minim velit minim ut duis qui officia consectetur ex nostrud ut elit elit nulla in consectetur voluptate aliqua aliqua.', + }, + // Payments + { + id: '81ac908c-35a2-4705-8d75-539863c35c09', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'Common PayPal, Skrill, and credit card issues', + answer: 'Sit occaecat sint nulla in esse dolor occaecat in ea sit irure magna magna veniam fugiat consequat exercitation ipsum ex officia velit consectetur consequat voluptate lorem eu proident lorem incididunt.\n\nExcepteur exercitation et qui labore nisi eu voluptate ipsum deserunt deserunt eu est minim dolor ad proident nulla reprehenderit culpa minim voluptate dolor nostrud dolor anim labore aliqua officia nostrud.', + }, + { + id: 'b6d8909f-f36d-4885-8848-46b8230d4476', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'How do I find my transaction ID?', + answer: 'Laboris ea nisi commodo nulla cillum consequat consectetur nisi velit adipisicing minim nulla culpa amet quis sit duis id id aliqua aute exercitation non reprehenderit aliquip enim eiusmod eu irure.\n\nNon irure consectetur sunt cillum do adipisicing excepteur labore proident ut officia dolor fugiat velit sint consectetur cillum qui amet enim anim mollit laboris consectetur non do laboris lorem aliqua.', + }, + { + id: '9496235d-4d0c-430b-817e-1cba96404f95', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'PayPal disputes And chargebacks', + answer: 'Ullamco eiusmod do pariatur pariatur consectetur commodo proident ex voluptate ullamco culpa commodo deserunt pariatur incididunt nisi magna dolor est minim eu ex voluptate deserunt labore id magna excepteur et.\n\nReprehenderit dolore pariatur exercitation ad non fugiat quis proident fugiat incididunt ea magna pariatur et exercitation tempor cillum eu consequat adipisicing est laborum sit cillum ea fugiat mollit cupidatat est.', + }, + { + id: '7fde17e6-4ac1-47dd-a363-2f4f14dcf76a', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'Saving your credit card details', + answer: 'Qui quis nulla excepteur voluptate elit culpa occaecat id ex do adipisicing est mollit id anim nisi irure amet officia ut sint aliquip dolore labore cupidatat magna laborum esse ea.\n\nEnim magna duis sit incididunt amet anim et nostrud laborum eiusmod et ea fugiat aliquip velit sit fugiat consectetur ipsum anim do enim excepteur cupidatat consequat sunt irure tempor ut.', + }, + { + id: '90a3ed58-e13b-40cf-9219-f933bf9c9b8f', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'Why do prepaid credits expire?', + answer: 'Consequat consectetur commodo deserunt sunt aliquip deserunt ex tempor esse nostrud sit dolore anim nostrud nulla dolore veniam minim laboris non dolor veniam lorem veniam deserunt laborum aute amet irure.\n\nEiusmod officia veniam reprehenderit ea aliquip velit anim aute minim aute nisi tempor qui sunt deserunt voluptate velit elit ut adipisicing ipsum et excepteur ipsum eu ullamco nisi esse dolor.', + }, + { + id: '153376ed-691f-4dfd-ae99-e204a49edc44', + categoryId: '71c34043-d89d-4aca-951d-8606c3943c43', + question: 'Why is there a minimum $20 credit?', + answer: 'Duis sint velit incididunt exercitation eiusmod nisi sunt ex est fugiat ad cupidatat sunt nisi elit do duis amet voluptate ipsum aliquip lorem aliqua sint esse in magna irure officia.\n\nNon eu ex elit ut est voluptate tempor amet ut officia in duis deserunt cillum labore do culpa id dolore magna anim consectetur qui consectetur fugiat labore mollit magna irure.', + }, + // Support + { + id: '4e7ce72f-863a-451f-9160-cbd4fbbc4c3d', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'What is item support?', + answer: 'Exercitation sit eiusmod enim officia exercitation eiusmod sunt eiusmod excepteur ad commodo eiusmod qui proident quis aliquip excepteur sit cillum occaecat non dolore sit in labore ut duis esse duis.\n\nConsequat sunt voluptate consectetur dolor laborum enim nostrud deserunt incididunt sint veniam laboris sunt amet velit anim duis aliqua sunt aliqua aute qui nisi mollit qui irure ullamco aliquip laborum.', + }, + { + id: '0795a74f-7a84-4edf-8d66-296cdef70003', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'How to contact an author', + answer: 'Minim commodo cillum do id qui irure aliqua laboris excepteur laboris magna enim est lorem consectetur tempor laboris proident proident eu irure dolor eiusmod in officia lorem quis laborum ullamco.\n\nQui excepteur ex sit esse dolore deserunt ullamco occaecat laboris fugiat cupidatat excepteur laboris amet dolore enim velit ipsum velit sint cupidatat consectetur cupidatat deserunt sit eu do ullamco quis.', + }, + { + id: '05532574-c102-4228-89a8-55fff32ec6fc', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'Extending and renewing item support', + answer: 'Reprehenderit anim consectetur anim dolor magna consequat excepteur tempor enim duis magna proident ullamco aute voluptate elit laborum mollit labore id ex lorem est mollit do qui ex labore nulla.\n\nUt proident elit proident adipisicing elit fugiat ex ullamco dolore excepteur excepteur labore laborum sunt ipsum proident magna ex voluptate laborum voluptate sint proident eu reprehenderit non excepteur quis eiusmod.', + }, + { + id: 'b3917466-aa51-4293-9d5b-120b0ce6635c', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'Rating or review removal policy', + answer: 'Ipsum officia mollit qui laboris sunt amet aliquip cupidatat minim non elit commodo eiusmod labore mollit pariatur aute reprehenderit ullamco occaecat enim pariatur aute amet occaecat incididunt irure ad ut.\n\nIncididunt cupidatat pariatur magna sint sit culpa ad cupidatat cillum exercitation consequat minim pariatur consectetur aliqua non adipisicing magna ad nulla ea do est nostrud eu aute id occaecat ut.', + }, + { + id: '2f2fb472-24d4-4a00-aa80-d513fa6c059c', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'Purchasing supported and unsupported items', + answer: 'Dolor cupidatat do qui in tempor dolor magna magna ut dolor est aute veniam consectetur enim sunt sunt duis magna magna aliquip id reprehenderit dolor in veniam ullamco incididunt occaecat.\n\nId duis pariatur anim cillum est sint non veniam voluptate deserunt anim nostrud duis voluptate occaecat elit ut veniam voluptate do qui est ad velit irure sint lorem ullamco aliqua.', + }, + { + id: '2fffd148-7644-466d-8737-7dde88c54154', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: "I haven't received a response from the author", + answer: 'Velit commodo pariatur ullamco elit sunt dolor quis irure amet tempor laboris labore tempor nisi consectetur ea proident dolore culpa nostrud esse amet commodo do esse laboris laboris in magna.\n\nAute officia labore minim laborum irure cupidatat occaecat laborum ex labore ipsum aliqua cillum do exercitation esse et veniam excepteur mollit incididunt ut qui irure culpa qui deserunt nostrud tempor.', + }, + { + id: '24a1034e-b4d6-4a86-a1ea-90516e87e810', + categoryId: 'bea49ee0-26da-46ad-97be-116cd7ab416d', + question: 'Responding to requests outside of support', + answer: 'Exercitation eu in officia lorem commodo pariatur pariatur nisi consectetur qui elit in aliquip et ullamco duis nostrud aute laborum laborum est dolor non qui amet deserunt ex et aliquip.\n\nProident consectetur eu amet minim labore anim ad non aute duis eiusmod sit ad elit magna do aliquip aliqua laborum dolor laboris ea irure duis mollit fugiat tempor eu est.', + }, +]; +export const guideCategories = [ + { + id: '0ee72de7-49c0-4880-9e89-b72a4edd6a81', + slug: 'getting-started', + title: 'Getting Started', + }, + { + id: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'projects', + title: 'Projects', + }, + { + id: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'settings', + title: 'Settings', + }, + { + id: '7b25b38c-1ab3-4474-8569-65b3ea232add', + slug: 'payments', + title: 'Payments', + }, + { + id: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'your-account', + title: 'Your Account', + }, +]; +export const guides = [ + // Getting started + { + id: 'a008ffa3-7b3f-43be-8a8f-dbf5272ed2dd', + categoryId: '0ee72de7-49c0-4880-9e89-b72a4edd6a81', + slug: 'what-is-this-app', + title: 'What is this app?', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '7643d388-12ab-4025-a2f1-5045ac7b1c4c', + categoryId: '0ee72de7-49c0-4880-9e89-b72a4edd6a81', + slug: 'start-using-the-app', + title: 'Start using the app', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '1fecee67-c4b4-413a-b0f2-949dcab73249', + categoryId: '0ee72de7-49c0-4880-9e89-b72a4edd6a81', + slug: 'signing-in-to-the-dashboard', + title: 'Signing in to the dashboard', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'd2e2ea8f-5298-4ba2-898b-afc60c064bba', + categoryId: '0ee72de7-49c0-4880-9e89-b72a4edd6a81', + slug: 'navigating-within-the-app', + title: 'Navigating within the app', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + // Projects + { + id: 'f2592886-11b8-4b56-baab-96802c2ed93e', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'creating-a-project', + title: 'Creating a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '9ec3f4b9-a355-4f57-9e93-efa8611cc1c9', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'renaming-a-project', + title: 'Renaming a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '1bc6e7f9-b046-4f4f-9b18-741c9d5429f6', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'displaying-a-project', + title: 'Displaying a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'a005d5f1-938d-45c5-8ed4-d0cf8d02e533', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'deleting-a-project', + title: 'Deleting a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '43837279-dce2-4dc0-beac-30b5ba829f14', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'changing-the-visibility-of-a-project', + title: 'Changing the visibility of a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '4cf5a435-eaa0-463c-8d2b-efde193c7fb3', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'adding-media-to-a-project', + title: 'Adding media to a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'cd3fb87e-e138-4721-9e29-a5c751bfd949', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'removing-a-media-from-a-project', + title: 'Removing a media from a project', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'f26205c6-882e-4713-b067-c73758b45551', + categoryId: '07b8421f-20bf-45b6-90ee-169ebe3a5bcc', + slug: 'cropping-a-media', + title: 'Cropping a media', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + // Settings + { + id: '1cbdeaeb-bbf1-4d04-b43d-f37b55e6a229', + categoryId: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'general-settings', + title: 'General settings', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '98de7d4a-2ca2-4d47-bbe6-083ed26467db', + categoryId: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'project-settings', + title: 'Project settings', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '145f497c-1fdb-47b5-a6c1-31f856403571', + categoryId: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'media-settings', + title: 'Media settings', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '0a007f59-a5ea-4875-991d-f22d6fd69898', + categoryId: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'domain-settings', + title: 'Domain settings', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '4707c8eb-31f9-415c-bd07-86f226c75feb', + categoryId: 'c88a1f54-360a-4b9b-a54b-2f92b7a1f63b', + slug: 'privacy-settings', + title: 'Privacy settings', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + // Payments + { + id: 'c771bf0a-1e0c-4b6d-af7e-189e10cc6fb8', + categoryId: '7b25b38c-1ab3-4474-8569-65b3ea232add', + slug: 'subscriptions', + title: 'Subscriptions', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '3d7150d2-feb3-4f20-bd3f-8e525cef77a4', + categoryId: '7b25b38c-1ab3-4474-8569-65b3ea232add', + slug: 'discounts', + title: 'Discounts', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '79239bc4-4fb5-428b-b30d-62c5289b061d', + categoryId: '7b25b38c-1ab3-4474-8569-65b3ea232add', + slug: 'payment-methods', + title: 'Payment methods', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '8d68c5e6-5404-450c-9d5f-d9800c164041', + categoryId: '7b25b38c-1ab3-4474-8569-65b3ea232add', + slug: 'overdue-payments', + title: 'Overdue payments', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + // Your account + { + id: '60df0d4c-dda1-439c-bd44-179c57a7597d', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'changing-your-username', + title: 'Changing your username', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '0a9c3321-1db3-42bc-92b6-7e257368123e', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'changing-your-email', + title: 'Changing your email', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '80ba5106-5f9c-4ed7-b8f3-8544035e3095', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'changing-your-password', + title: 'Changing your password', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'db2e97a6-d657-4e9d-9b6c-5f213ea3301c', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'closing-your-account', + title: 'Closing your account', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: '3374c887-2fb7-4223-9f40-7f2cbbf76795', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'account-limits', + title: 'Account limits', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, + { + id: 'cc65f92a-7d46-4557-b15b-6f8f59a60576', + categoryId: '41fdf071-aec4-49de-9dd4-b4f746596928', + slug: 'two-factor-authentication', + title: 'Two factor authentication', + subtitle: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt', + }, +]; + +// Since we only have one content for the demo, we will +// use the following mock-api on every request for every guide. +export const guideContent = ` +

Header Level 2

+ +

+ Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit + amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper + pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, + sagittis tempus lacus enim ac dui. Donec non enim + in turpis pulvinar facilisis. Ut felis. +

+ +

+ Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos + himenaeos. Curabitur vitae sagittis odio. Suspendisse ullamcorper nunc non pellentesque laoreet. Curabitur eu tortor id quam pretium mattis. Proin ut quam velit. +

+ +

Header Level 3

+ + +

+ Nullam sagittis nulla in diam finibus, sed pharetra velit vestibulum. Suspendisse euismod in urna eu posuere. +

+ +

Header Level 4

+ +
+

+ Blockquote. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur + massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est. +

+
+ Username +
+
+ +
    +
  1. Ordered list
  2. +
  3. Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
  4. +
  5. Aliquam tincidunt mauris eu risus.
  6. +
+ +
Header Level 5
+ +
    +
  • Unordered list
  • +
  • Lorem ipsum dolor sit amet, consectetuer adipiscing elit.
  • +
  • Aliquam tincidunt mauris eu risus.
  • +
+ +
#header h1 a {
+    display: block;
+    width: 300px;
+    height: 80px;
+}
+ +
Header Level 6
+ +
+
Definition list
+
+ Quisque sit amet risus enim. Aliquam sit amet interdum justo, at ultricies sapien. Suspendisse et semper urna, in gravida eros. Quisque id nibh iaculis, euismod urna sed, + egestas nisi. Donec eros metus, congue a imperdiet feugiat, sagittis nec ipsum. Quisque dapibus mollis felis non tristique. +
+ +
Definition list
+
+ Ut auctor, metus sed dapibus tempus, urna diam auctor odio, in malesuada odio risus vitae nisi. Etiam blandit ante urna, vitae placerat massa mollis in. Duis nec urna ac + purus semper dictum ut eget justo. Aenean non sagittis augue. Sed venenatis rhoncus enim eget ornare. Donec viverra sed felis at venenatis. Mauris aliquam fringilla nulla, + sit amet congue felis dignissim at. +
+
`; diff --git a/src/app/mock-api/apps/mailbox/api.ts b/src/app/mock-api/apps/mailbox/api.ts new file mode 100644 index 0000000..a7d4b3f --- /dev/null +++ b/src/app/mock-api/apps/mailbox/api.ts @@ -0,0 +1,373 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api'; +import { + filters as filtersData, + folders as foldersData, + labels as labelsData, + mails as mailsData, + settings as settingsData, +} from 'app/mock-api/apps/mailbox/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class MailboxMockApi { + private _filters: any[] = filtersData; + private _folders: any[] = foldersData; + private _mails: any[] = mailsData; + private _labels: any[] = labelsData; + private _settings: any = settingsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Settings - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/mailbox/settings') + .reply(() => [200, cloneDeep(this._settings)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Settings - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/mailbox/settings') + .reply(({ request }) => { + // Get the settings + const settings = cloneDeep(request.body.settings); + + // Update the settings + this._settings = assign({}, this._settings, settings); + + // Return the response + return [200, cloneDeep(this._settings)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Folders - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/mailbox/folders').reply(() => { + let count = 0; + + // Iterate through the folders + this._folders.forEach((folder) => { + // Get the mails of this folder + const mails = this._mails.filter( + (mail) => mail.folder === folder.id + ); + + // If we are counting the 'sent' or the 'trash' folder... + if (folder.slug === 'sent' || folder.slug === 'trash') { + // Always set the count to 0 + count = 0; + } + // If we are counting the 'drafts' or the 'spam' folder... + else if ( + folder.slug === 'drafts' || + folder.slug === 'trash' || + folder.slug === 'spam' + ) { + // Set the count to the count of all mails + count = mails.length; + } + // Otherwise ('inbox')... + else { + // Go through the mails and count the unread ones + mails.forEach((mail) => { + if (mail.unread) { + count++; + } + }); + } + + // Append the count to the folder mock-api + folder.count = count; + + // Reset the count + count = 0; + }); + + // Return the response + return [200, cloneDeep(this._folders)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Filters - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/mailbox/filters') + .reply(() => [200, cloneDeep(this._filters)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/mailbox/labels') + .reply(() => [200, cloneDeep(this._labels)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/mailbox/label') + .reply(({ request }) => { + // Get the label + const label = cloneDeep(request.body.label); + + // Generate an id + label.id = AngorMockApiUtils.guid(); + + // Generate a slug + label.slug = label.title + .toLowerCase() + .replace(/ /g, '-') + .replace(/[-]+/g, '-') + .replace(/[^\w-]+/g, ''); + + // Check if the slug is being used and update it if necessary + const originalSlug = label.slug; + + let sameSlug; + let slugSuffix = 1; + + do { + sameSlug = this._labels.filter( + (item) => item.slug === label.slug + ); + + if (sameSlug.length > 0) { + label.slug = originalSlug + '-' + slugSuffix; + slugSuffix++; + } + } while (sameSlug.length > 0); + + // Add the label + this._labels.push(label); + + // Return the response + return [200, label]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/mailbox/label') + .reply(({ request }) => { + // Get the id and label + const id = request.body.id; + const label = cloneDeep(request.body.label); + + // Prepare the updated label + let updatedLabel = null; + + // Find the label and update it + this._labels.forEach((item, index, labels) => { + if (item.id === id) { + // Update the slug + label.slug = label.title + .toLowerCase() + .replace(/ /g, '-') + .replace(/[-]+/g, '-') + .replace(/[^\w-]+/g, ''); + + // Update the label + labels[index] = assign({}, labels[index], label); + + // Store the updated label + updatedLabel = labels[index]; + } + }); + + // Return the response + return [200, updatedLabel]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/mailbox/label') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the label and delete it + const index = this._labels.findIndex((item) => item.id === id); + this._labels.splice(index, 1); + + // Get all the mails that have the label + const mailsWithLabel = this._mails.filter( + (mail) => mail.labels.indexOf(id) > -1 + ); + + // Iterate through them and remove the label + mailsWithLabel.forEach((mail) => { + mail.labels.splice(mail.labels.indexOf(id), 1); + }); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Mails - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/mailbox/mails', 625) + .reply(({ request }) => { + // First, decide if mails are requested by folder, filter or label + const byFolder = request.params.get('folder'); + const byFilter = request.params.get('filter'); + const byLabel = request.params.get('label'); + + // Clone the mails mock-api to prevent accidental mock-api updates + let mails: any[] | null = cloneDeep(this._mails); + + // Filter the mails depending on the requested by type + mails = mails.filter((mail) => { + if (byFolder) { + return ( + mail.folder === + this._folders.find( + (folder) => folder.slug === byFolder + ).id + ); + } + + if (byFilter) { + return mail[byFilter] === true; + } + + if (byLabel) { + return mail.labels.includes( + this._labels.find((label) => label.slug === byLabel) + .id + ); + } + }); + + // Sort by date - descending + mails.sort( + (a, b) => + new Date(b.date).getTime() - new Date(a.date).getTime() + ); + + // Figure out the cc and bcc counts + mails.forEach((mail) => { + mail.ccCount = mail.cc ? mail.cc.length : 0; + mail.bccCount = mail.bcc ? mail.bcc.length : 0; + }); + + // Paginate - Start + const mailsLength = mails.length; + const resultsPerPage = 10; + + // Get the requested page number + const page = parseInt(request.params.get('page') ?? '1', 10); + + // Calculate pagination details + const begin = (page - 1) * resultsPerPage; + const end = Math.min(resultsPerPage * page, mailsLength); + const lastPage = Math.max( + Math.ceil(mailsLength / resultsPerPage), + 1 + ); + + // Prepare the pagination object + let pagination = {}; + + // If the requested page number is bigger than + // the last possible page number, return null for + // mails but also send the last possible page so + // the app can navigate to there + if (page > lastPage) { + mails = null; + pagination = { + lastPage, + }; + } else { + // Paginate the results by 10 + mails = mails.slice(begin, end); + + // Prepare the pagination mock-api + pagination = { + totalResults: mailsLength, + resultsPerPage: resultsPerPage, + currentPage: page, + lastPage: lastPage, + startIndex: begin, + endIndex: end - 1, + }; + } + + // Return the response + return [ + 200, + { + mails, + pagination, + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Mail - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/mailbox/mail') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the mails mock-api to prevent accidental mock-api updates + const mails = cloneDeep(this._mails); + + // Find the mail + const mail = mails.find((item) => item.id === id); + + return [200, mail]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Mail - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/mailbox/mail') + .reply(({ request }) => { + // Get the id and mail + const id = request.body.id; + const mail = cloneDeep(request.body.mail); + + // Prepare the updated mail + let updatedMail = null; + + // Find the mail and update it + this._mails.forEach((item, index, mails) => { + if (item.id === id) { + // Update the mail + mails[index] = assign({}, mails[index], mail); + + // Store the updated mail + updatedMail = mails[index]; + } + }); + + // Return the response + return [200, updatedMail]; + }); + } +} diff --git a/src/app/mock-api/apps/mailbox/data.ts b/src/app/mock-api/apps/mailbox/data.ts new file mode 100644 index 0000000..cbf7fa2 --- /dev/null +++ b/src/app/mock-api/apps/mailbox/data.ts @@ -0,0 +1,2669 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const folders = [ + { + id: '7c004a19-4506-48ef-93ab-f16381302e3b', + title: 'Inbox', + slug: 'inbox', + icon: 'heroicons_outline:inbox', + }, + { + id: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + title: 'Sent', + slug: 'sent', + icon: 'heroicons_outline:paper-airplane', + }, + { + id: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + title: 'Drafts', + slug: 'drafts', + icon: 'heroicons_outline:document', + }, + { + id: '0197c436-2ef3-424d-b546-8b7f49186e15', + title: 'Spam', + slug: 'spam', + icon: 'heroicons_outline:exclamation-triangle', + }, + { + id: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + title: 'Trash', + slug: 'trash', + icon: 'heroicons_outline:trash', + }, +]; +export const filters = [ + { + id: 'de1b41f6-6839-4f1b-9d2c-07e55f6f8f82', + title: 'Starred', + slug: 'starred', + icon: 'heroicons_outline:star', + }, + { + id: '71bba1ec-a90e-4a71-9932-4bab0a99aa1c', + title: 'Important', + slug: 'important', + icon: 'heroicons_outline:exclamation-circle', + }, +]; +export const labels = [ + { + id: 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + title: 'Personal', + slug: 'personal', + color: 'blue', + }, + { + id: '745cf30e-ca84-47a1-a553-b70eb630d8e7', + title: 'Work', + slug: 'work', + color: 'indigo', + }, + { + id: '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + title: 'Payments', + slug: 'payments', + color: 'red', + }, + { + id: 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + title: 'Invoices', + slug: 'invoices', + color: 'teal', + }, + { + id: '184cd689-4ee4-47cf-9f8a-12233d614326', + title: 'Accounts', + slug: 'accounts', + color: 'purple', + }, + { + id: 'b67fc437-6118-4ec8-a3c7-9320b828e3fc', + title: 'Forums', + slug: 'forums', + color: 'green', + }, +]; +export const settings = { + messageLayout: 'right', +}; +export const mails = [ + { + id: 'f9c4c091-3ac4-4df9-ac5d-aec7b07c8e3f', + type: 'mail', + from: { + avatar: 'images/avatars/female-01.jpg', + contact: 'Myra Dudley ', + }, + to: 'me ', + cc: ['Graham Belltower '], + bcc: ['Julie T. '], + date: now + .set({ + hour: 20, + minute: 13, + }) + .toISO(), // Today - 20:13 + subject: 'Please review and sign the attached agreement', + content: + 'Hi Brian,\n\nUllamco deserunt commodo esse deserunt deserunt quis eiusmod. Laborum sint excepteur non sit eiusmod sunt voluptate ipsum nisi ullamco magna. Lorem consectetur est dolor minim exercitation deserunt quis duis fugiat ipsum incididunt non. Anim aute ipsum cupidatat nisi occaecat quis sit nisi labore labore dolore do. Pariatur veniam culpa quis veniam nisi exercitation veniam ut. Quis do sint proident fugiat ad.\n\nNon id nisi commodo veniam. Veniam veniam minim ea laborum voluptate id duis deserunt. Anim ut ut amet et ullamco nulla fugiat id incididunt adipisicing excepteur amet. Ex amet eu cillum non fugiat velit dolore. Incididunt duis est eu et ex sunt consectetur cillum nisi aute proident.\n\nIncididunt excepteur laborum quis sit. Ex quis officia incididunt proident aliqua adipisicing. Irure ad in Lorem laborum deserunt nulla consequat. Pariatur excepteur exercitation cupidatat aute.\n\nCheers!\nMyra Dudley', + attachments: [ + { + type: 'image/jpeg', + name: 'mystery-forest.jpg', + size: 15539, + preview: 'mystery-forest_preview.jpg', + downloadUrl: '', + }, + { + type: 'application/pdf', + name: 'montly-invoice.pdf', + size: 243449, + preview: 'pdf', + downloadUrl: '', + }, + { + type: 'image/jpeg', + name: 'birds-eye-sydney.jpg', + size: 14294, + preview: 'birds-eye-sydney_preview.jpg', + downloadUrl: '', + }, + ], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'c531bc01-8a9e-481b-adf8-95303a6938c5', + type: 'mail', + from: { + avatar: 'images/avatars/male-01.jpg', + contact: 'Shaw Murray ', + }, + to: 'me ', + date: now + .set({ + hour: 18, + minute: 56, + }) + .toISO(), // Today - 18:56 + subject: 'Delivery address confirmation', + content: + 'Dear Brian,\n\nDolore consectetur est cupidatat ipsum reprehenderit anim quis veniam anim ipsum incididunt exercitation. Velit exercitation culpa eiusmod dolore labore irure. Duis esse quis elit pariatur labore occaecat esse voluptate dolore deserunt cillum irure. Aute qui nulla est exercitation qui sunt anim aliquip. Ex ad est velit laboris exercitation ea ut pariatur. Amet reprehenderit ut est id sunt commodo anim et est voluptate et.\n\nMagna aliqua incididunt non ut voluptate nulla aliqua exercitation elit consectetur cupidatat. Proident in reprehenderit occaecat laborum non eu amet id aliqua nulla dolore. Eiusmod quis adipisicing quis cupidatat labore.\n\nReprehenderit nulla ullamco est dolore ex irure sunt nostrud reprehenderit quis dolor. Tempor nostrud elit elit aute ut ut eiusmod laboris excepteur consequat ex. Velit id ex ullamco in. Ea elit Lorem Lorem aliquip amet consequat irure nisi qui cillum incididunt. Commodo aute Lorem eiusmod veniam consectetur aute eu dolore. Ea magna incididunt laboris quis quis et tempor dolore dolore ut nisi.\n\nBest Regards,\nShaw Murray', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'ebc80fc3-6c56-4cae-a45a-771b15ced076', + type: 'mail', + from: { + avatar: 'images/avatars/male-02.jpg', + contact: 'Sanders Beck ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: now + .set({ + hour: 14, + minute: 35, + }) + .toISO(), // Today - 14:35 + subject: 'Insurance documents', + content: + 'Hi Brian,\n\nAliquip ipsum sunt sit sunt velit velit pariatur. Nisi incididunt eiusmod consequat ut cillum eu exercitation. Enim proident nostrud aute in. Non irure nisi duis aliquip commodo proident veniam adipisicing id velit. Enim magna Lorem fugiat tempor.\n\nCommodo non nulla incididunt irure voluptate. Fugiat culpa cillum aute quis. Voluptate veniam adipisicing dolor sint. Proident eiusmod quis duis ipsum sit eu.\n\nDeserunt reprehenderit adipisicing reprehenderit ipsum. Laborum in veniam amet occaecat tempor esse enim dolore elit sit quis adipisicing. Aute occaecat eiusmod enim cupidatat sunt.\n\nBest Regards,\nSanders Beck', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: '981c5ffb-7c88-47a8-b60f-f16150eeae9d', + type: 'mail', + from: { + avatar: 'images/avatars/male-03.jpg', + contact: 'Zimmerman Gould ', + }, + to: 'me ', + date: now + .minus({ day: 1 }) + .set({ + hour: 22, + minute: 26, + }) + .toISO(), // Yesterday - 22:26 + subject: 'Previous clients and their invoices', + content: + 'Dear Brian,\n\nDo aute eu dolore officia laborum id anim fugiat incididunt nulla esse proident. Veniam veniam nostrud ut nisi magna ipsum ea eiusmod esse velit id aliqua nisi irure. Amet laborum fugiat deserunt est. Quis amet veniam anim nostrud irure cillum voluptate consequat qui cupidatat minim occaecat elit enim. Ut ut incididunt cillum sit sit irure culpa. Culpa exercitation minim velit eu. Ipsum exercitation excepteur et ad do sit.\n\nVeniam cupidatat officia aliqua ad excepteur cillum laboris deserunt esse laboris adipisicing reprehenderit. Reprehenderit anim consectetur pariatur labore do in irure. Ad consequat commodo non pariatur occaecat. Eiusmod cillum non anim consequat culpa nisi. Est nulla ut sint qui deserunt anim. Excepteur qui occaecat dolore nulla occaecat cupidatat aute sit laborum magna.\n\nConsequat aliqua commodo officia excepteur. Ex consectetur elit dolor exercitation ullamco amet laboris. Deserunt nulla non proident est pariatur reprehenderit reprehenderit. Ea nisi id aliqua cillum velit tempor ipsum dolor proident cillum eiusmod et ipsum anim. Elit non quis mollit enim Lorem cupidatat et labore. Laboris cillum reprehenderit aute veniam aliqua esse officia proident deserunt. Eiusmod laboris ullamco amet consectetur amet.\n\nKind Regards,\nZimmerman Gould', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'a8d0645d-ac30-4f1a-a163-06e949120289', + type: 'mail', + from: { + avatar: 'images/avatars/female-02.jpg', + contact: 'Karina Alford ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: now + .minus({ day: 1 }) + .set({ + hour: 20, + minute: 5, + }) + .toISO(), // Yesterday - 20:05 + subject: 'Quote for a new web design project', + content: + 'Hey Brian,\n\nNisi officia aliqua ex non cupidatat sint ullamco. Irure pariatur ullamco consequat ut eu anim. Ut ad elit pariatur est non sunt. Tempor dolore quis commodo dolore duis officia laboris nostrud sint. Exercitation ullamco laboris eiusmod culpa ut.\n\nAute Lorem aute occaecat dolore tempor ipsum proident fugiat deserunt non incididunt velit nulla. Dolor pariatur tempor amet qui eu exercitation. Tempor minim culpa proident nisi esse ea. Enim est fugiat aliqua aliqua aute velit laborum cupidatat irure nisi dolor deserunt aliqua.\n\nFugiat ut dolor tempor sunt aliquip dolor nostrud. Consequat incididunt ullamco cillum dolore excepteur deserunt est dolor aliquip irure do mollit officia. Consectetur cillum et non minim nisi. Esse quis sunt deserunt elit sint velit tempor et ullamco laboris officia excepteur. Veniam ad ut aliqua sunt consequat reprehenderit nostrud non in duis aute quis pariatur. Occaecat mollit anim non pariatur. Ad do ad id fugiat et culpa laborum esse cupidatat voluptate elit ut magna voluptate.\n\nBest Regards,\nKarina Alford', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'fd117ed9-1285-4aca-8c1c-5c96e732c558', + type: 'mail', + from: { + avatar: 'images/avatars/female-03.jpg', + contact: 'Carla Gray ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: now + .minus({ day: 1 }) + .set({ + hour: 16, + minute: 43, + }) + .toISO(), // Yesterday - 16:43 + subject: + 'Nulla culpa consectetur aute ex eu irure incididunt aliqua cupidatat sit cillum fugiat anim ea', + content: + 'Hey Brian,\n\nDo pariatur occaecat tempor duis. Aute occaecat non consequat ut occaecat sint. Cillum reprehenderit elit nisi incididunt in labore pariatur. Labore mollit pariatur nulla officia esse anim exercitation nisi commodo culpa laborum amet nisi.\n\nSunt culpa mollit nostrud excepteur adipisicing sit do. Cillum voluptate amet do sit quis aliquip ea est qui elit. Veniam exercitation sit reprehenderit labore officia in labore excepteur eiusmod exercitation.\n\nEnim nostrud est non esse reprehenderit in ea eiusmod. Duis incididunt amet aliquip dolor esse. Nostrud qui commodo in non nostrud proident enim cupidatat. Aute sunt aliqua excepteur qui occaecat nulla incididunt commodo adipisicing ipsum.\n\nKind Regards,\nCarla Gray', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'a307d83b-d256-4af5-948a-148878a7eaad', + type: 'mail', + from: { + avatar: 'images/avatars/male-04.jpg', + contact: 'Rice Cash ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: now + .minus({ day: 2 }) + .set({ + hour: 11, + minute: 28, + }) + .toISO(), // 2 days ago - 11:28 + subject: 'Ipsum laborum minim aute labore in', + content: + 'Dear Brian,\n\nLaboris non ad et aute sint aliquip mollit voluptate velit dolore magna fugiat ex. Voluptate amet aute deserunt tempor non laboris cillum. Voluptate veniam magna sint magna proident exercitation adipisicing aute id ad tempor reprehenderit magna ullamco. Laborum Lorem anim elit aliquip ut aute minim fugiat aliquip. Eiusmod est et occaecat dolore anim laborum ullamco ipsum commodo.\n\nCommodo amet veniam nostrud mollit quis sint qui nulla elit esse excepteur ullamco esse magna. Nisi duis aute est in mollit irure enim tempor in. Mollit ipsum laboris et velit ex excepteur pariatur. Cillum veniam id ipsum magna. Laborum duis aliquip ut ipsum ad aliqua id sit pariatur consequat sit. Sit nulla nulla ullamco nulla eiusmod et in dolore sint reprehenderit cupidatat.\n\nIpsum mollit cupidatat magna occaecat labore est fugiat est fugiat fugiat nulla labore laboris. Eiusmod aute adipisicing pariatur aliquip sint enim anim in dolore enim aute culpa nulla. Minim magna enim officia ipsum elit quis do velit deserunt Lorem veniam excepteur.\n\nKind Regards,\nRice Cash', + attachments: [ + { + type: 'image/png', + name: 'lake-of-carezza.png', + size: 13071, + preview: 'lake-of-carrezza_preview.png', + downloadUrl: '', + }, + { + type: 'image/jpeg', + name: 'birds-eye-sydney.jpg', + size: 14294, + preview: 'birds-eye-sydney_preview.jpg', + downloadUrl: '', + }, + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + ], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: '67664fa3-3a87-4ab8-8c2c-dfd2b1de4c14', + type: 'mail', + from: { + avatar: 'images/avatars/female-04.jpg', + contact: 'Elaine Ortiz ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: now + .minus({ day: 2 }) + .set({ + hour: 7, + minute: 12, + }) + .toISO(), // 2 days ago - 07:12 + subject: 'Ipsum fugiat ad deserunt cillum sunt fugiat', + content: + 'Hello Brian,\n\nId Lorem laborum eiusmod eiusmod mollit magna dolore. Et commodo officia fugiat dolor aliqua proident mollit ut commodo ullamco. Sunt nulla eu dolor velit velit reprehenderit. Culpa esse veniam fugiat eiusmod id veniam sunt reprehenderit minim mollit. Esse qui ea irure pariatur eu ullamco pariatur ipsum reprehenderit proident mollit proident. Nisi fugiat ut est aliquip nulla in non dolore.\n\nCulpa irure cillum ex fugiat cupidatat eiusmod non. Qui irure velit consectetur minim eu excepteur eiusmod veniam irure ad culpa nisi. Nisi sit nostrud quis ullamco aliquip non consequat sunt reprehenderit velit dolor dolor laboris. Dolore in Lorem consectetur nostrud. Laborum cupidatat exercitation voluptate duis amet. Sunt sint minim do in commodo ipsum commodo ea qui velit deserunt qui anim fugiat.\n\nExercitation et qui consequat incididunt nisi incididunt cupidatat officia in. Sit eiusmod anim aliqua elit. Nisi mollit ut non pariatur enim fugiat sint labore velit nostrud eu. Eiusmod id laboris laboris duis enim aute ipsum in magna. Sit eiusmod amet duis commodo sint et anim ex sunt deserunt dolor incididunt. Eiusmod duis dolore dolor elit occaecat do adipisicing ullamco ex laboris aliqua adipisicing. Labore pariatur aute proident mollit elit commodo labore minim dolore non in cillum.\n\nCheers!\nElaine Ortiz', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'd5913a7e-25f8-4163-bbf0-81d034163ce7', + type: 'mail', + from: { + avatar: 'images/avatars/male-05.jpg', + contact: 'Fleming Stone ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: now + .minus({ day: 2 }) + .set({ + hour: 6, + minute: 1, + }) + .toISO(), // 2 days ago - 06:01 + subject: 'Deserunt exercitation ut nulla elit Lorem', + content: + 'Hi Brian,\n\nEst labore sunt sunt Lorem dolore. In excepteur esse proident ut consectetur dolor voluptate laborum veniam pariatur. Excepteur ut veniam sit culpa exercitation qui nulla nulla magna ea in dolore et consequat. Irure minim ad cupidatat amet reprehenderit excepteur incididunt nulla eu et excepteur anim et aliqua.\n\nSint sint Lorem magna est irure sint ea cupidatat fugiat. Occaecat non adipisicing magna magna culpa sit commodo aute ex consequat amet minim esse ut. In nulla eiusmod veniam deserunt in.\n\nIn aute excepteur qui pariatur fugiat. Occaecat velit voluptate proident occaecat ut laboris occaecat pariatur aute dolore do. Ut commodo ipsum est non commodo ut ea qui labore veniam. Occaecat nostrud eu dolor tempor velit excepteur sint occaecat excepteur aliqua aliquip. Magna mollit ea aliquip exercitation do elit ex reprehenderit esse aliqua elit.\n\nKind Regards,\nFleming Stone', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'b099a8e2-ffcc-4ae1-866d-8f8f6bd95ab3', + type: 'mail', + from: { + avatar: 'images/avatars/male-06.jpg', + contact: 'England Wiley ', + }, + to: 'me ', + date: now + .minus({ day: 5 }) + .set({ + hour: 15, + minute: 36, + }) + .toISO(), // 5 days ago - 15:36 + subject: + 'Minim do reprehenderit dolor ipsum officia magna laborum est anim in fugiat', + content: + 'Dear Brian,\n\nAd do minim id ad ex sit reprehenderit labore do occaecat fugiat ut enim. Et sunt dolore sint non consequat ut. Esse deserunt nostrud pariatur nulla ullamco nulla sit aliquip culpa sunt ipsum. Ut ad minim qui anim amet aute cupidatat. Est ullamco duis laboris nulla labore incididunt consectetur. Cillum sunt mollit nulla laborum non tempor veniam consequat.\n\nAmet fugiat velit id deserunt pariatur velit laboris consectetur quis officia. Culpa nostrud deserunt nostrud esse labore esse consequat labore fugiat. Nostrud duis ex nulla et do.\n\nPariatur mollit ex adipisicing nostrud nostrud occaecat. Id tempor irure cupidatat duis cillum cupidatat nostrud enim anim. Esse nisi pariatur nisi elit elit sit quis ullamco dolor dolore pariatur est sint. Sint ex aliqua id sunt sunt magna amet ex sit anim. Irure aliquip fugiat ipsum tempor irure nisi Lorem anim sit ullamco. Exercitation nostrud mollit est non enim.\n\nBest Regards,\nEngland Wiley', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '7bd21940-3388-479c-b1bc-3ebceb0472d8', + type: 'mail', + from: { + avatar: 'images/avatars/male-07.jpg', + contact: 'Ingram Fowler ', + }, + to: 'me ', + date: new Date('Sun Jan 07 2018 03:51:20 GMT+0000 (UTC)').toISOString(), + subject: + 'Aliquip eiusmod pariatur adipisicing id consectetur sunt ad dolore consequat commodo', + content: + 'Dear Brian,\n\nDolore sit occaecat est do fugiat sunt est amet nostrud. Aliqua ad veniam officia Lorem id aute fugiat laborum dolor magna dolor. Eiusmod nostrud qui sunt ut exercitation deserunt ipsum. Commodo veniam velit reprehenderit minim amet occaecat consectetur sint aliquip Lorem voluptate cupidatat. Aute aliquip do veniam nostrud nisi minim amet. Ex id ullamco non ea ullamco cillum et Lorem sunt sunt officia dolore excepteur.\n\nSit enim anim occaecat eu adipisicing velit ut excepteur consectetur sunt. Non fugiat deserunt quis fugiat eiusmod magna voluptate nisi commodo minim sunt dolore consequat labore. Pariatur ad aliqua do non labore exercitation aute minim culpa adipisicing qui. Anim et et anim dolore consequat fugiat amet aliquip nisi aliqua irure occaecat et laboris. Aute aliquip incididunt sit ipsum do. Ullamco in anim laboris incididunt tempor duis irure ipsum cillum duis ea. Magna culpa adipisicing ad ullamco id consequat qui ullamco cupidatat pariatur.\n\nMollit amet enim sint cupidatat eu aute exercitation dolor. Minim exercitation nostrud ullamco magna laboris. Pariatur proident aute proident et. Officia cillum pariatur nisi sint anim officia. Sunt minim anim ad tempor deserunt commodo magna labore incididunt ex ad nulla nulla ut. Sint ipsum aliqua dolor mollit do anim officia incididunt. Irure nulla ex elit id pariatur dolore et nostrud occaecat.\n\nCheers!\nIngram Fowler', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: '2d105bae-b4e5-4ba3-a40e-e9e2b5cc671a', + type: 'mail', + from: { + avatar: 'images/avatars/female-05.jpg', + contact: 'Diana Walsh ', + }, + to: 'me ', + date: new Date('Fri Jun 29 2018 07:37:52 GMT+0000 (UTC)').toISOString(), + subject: 'Non anim id laborum in et id', + content: + 'Dear Brian,\n\nTempor veniam do dolor laborum consectetur in sit incididunt nulla officia consectetur fugiat. In dolor consequat consectetur deserunt sit. Voluptate reprehenderit tempor dolor dolore nulla aliquip commodo elit cillum laboris occaecat laboris. Eu dolor magna velit ea commodo dolor. Occaecat sit mollit amet voluptate eiusmod aliqua sunt irure sunt fugiat ipsum eu. Consequat ea sit consequat esse.\n\nAdipisicing adipisicing voluptate duis ullamco sint anim sunt nostrud deserunt minim velit aute nisi et. Do ea cupidatat culpa eu qui. Lorem enim laboris amet officia fugiat nisi Lorem laborum ex. Aliquip nostrud sit esse nisi labore.\n\nId amet tempor tempor Lorem fugiat culpa. Elit nulla pariatur adipisicing proident. In qui esse eiusmod ad est minim ipsum mollit aute mollit ad duis aliqua.\n\nCheers!\nDiana Walsh', + attachments: [ + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + { + type: 'application/pdf', + name: 'montly-invoice.pdf', + size: 243449, + preview: 'pdf', + downloadUrl: '', + }, + { + type: 'image/jpeg', + name: 'mystery-forest.jpg', + size: 15539, + preview: 'mystery-forest_preview.jpg', + downloadUrl: '', + }, + ], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '4c3bd79a-6429-466d-b962-8eb09c524969', + type: 'mail', + from: { + avatar: 'images/avatars/male-08.jpg', + contact: 'Mckinney Marsh ', + }, + to: 'me ', + date: new Date('Wed Jun 20 2018 15:24:03 GMT+0000 (UTC)').toISOString(), + subject: + 'Adipisicing proident laborum qui deserunt adipisicing exercitation id sint', + content: + 'Hi Brian,\n\nAmet eiusmod est ipsum fugiat. Laborum dolor exercitation esse nostrud cillum. Sunt laboris culpa incididunt ullamco sint veniam dolore tempor non irure ipsum. Laborum quis dolore dolor veniam quis exercitation sint dolore tempor occaecat pariatur officia. Non labore consectetur elit laborum exercitation ut exercitation pariatur Lorem.\n\nExercitation cillum sint exercitation incididunt laboris ut veniam irure sit. Id voluptate esse dolore in fugiat sit sint labore ex ea. Lorem laborum officia occaecat ipsum adipisicing do nostrud proident. Adipisicing fugiat anim aute amet consequat labore non et enim veniam anim. Elit do pariatur pariatur nulla consectetur sit anim cillum cillum.\n\nId qui pariatur enim laborum eu qui. Fugiat sint duis nisi culpa non. Labore cupidatat magna dolor eu et. Anim nulla elit ut eiusmod et excepteur aute culpa labore aliquip.\n\nCheers!\nMckinney Marsh', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '770d24d1-1b9b-49ec-bcb4-f6feffc305ff', + type: 'mail', + from: { + avatar: 'images/avatars/male-09.jpg', + contact: 'Meyer Fuller ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Wed Jan 31 2018 08:17:08 GMT+0000 (UTC)').toISOString(), + subject: 'Excepteur sunt ut ipsum ad culpa aliqua quis', + content: + 'Hey Brian,\n\nCupidatat cupidatat irure culpa est dolore qui laborum adipisicing occaecat nulla officia deserunt fugiat aliqua. Dolor quis sunt aliqua officia culpa esse eiusmod eiusmod ad laboris. Sit deserunt cillum ad cillum minim officia in velit fugiat aliqua ullamco duis elit. Anim incididunt consequat ex amet duis tempor voluptate cillum officia exercitation culpa dolor enim.\n\nEa velit minim officia fugiat culpa nostrud. Ex aute amet veniam anim consequat dolor Lorem sint. Sunt culpa cillum magna est veniam adipisicing. Reprehenderit eu tempor duis veniam velit Lorem elit amet amet ut anim do dolor.\n\nOfficia minim eiusmod et reprehenderit est proident aute amet non nulla fugiat. Proident enim ea cupidatat dolore ea id ad. Qui et eu adipisicing esse mollit mollit exercitation velit in. Consequat mollit magna est quis est duis proident sunt eu officia reprehenderit. Elit esse incididunt adipisicing consequat culpa aliquip deserunt dolore ullamco velit mollit sit sit Lorem. Do quis qui quis veniam aliqua consequat excepteur.\n\nCheers!\nMeyer Fuller', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '3e2100de-ca0a-4a8e-a1c5-6c13172333dc', + type: 'mail', + from: { + avatar: 'images/avatars/female-06.jpg', + contact: 'Carolina Wade ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sat Mar 24 2018 17:50:27 GMT+0000 (UTC)').toISOString(), + subject: 'In sunt pariatur sunt sint exercitation', + content: + 'Hey Brian,\n\nReprehenderit proident mollit non eu mollit eu. Mollit exercitation non enim commodo sit eu eiusmod est cupidatat esse magna sint quis dolore. Esse deserunt ea sunt quis tempor est deserunt qui proident Lorem. Adipisicing dolore non laboris proident. Incididunt fugiat labore proident eu et ad magna tempor ipsum nostrud adipisicing eiusmod eu.\n\nNisi excepteur ullamco minim laboris sit labore tempor officia commodo officia sit enim qui occaecat. Quis ullamco enim minim voluptate consectetur mollit elit voluptate fugiat. Laboris sint eu magna ullamco laboris aliquip duis laboris sit enim reprehenderit occaecat labore. Mollit nulla magna et labore officia et voluptate fugiat non commodo esse et laboris exercitation.\n\nLaboris amet Lorem sint in. Quis nulla sit et non qui fugiat et culpa pariatur incididunt duis. Dolor tempor incididunt Lorem irure anim velit tempor voluptate.\n\nKind Regards,\nCarolina Wade', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'e1291d1a-fba6-4b23-b259-dd7c9074e976', + type: 'mail', + from: { + avatar: 'images/avatars/male-10.jpg', + contact: 'Graves Huber ', + }, + to: 'me ', + date: new Date('Sun Mar 25 2018 02:46:44 GMT+0000 (UTC)').toISOString(), + subject: + 'Elit est aute anim ea culpa labore occaecat adipisicing officia', + content: + 'Dear Brian,\n\nAd ex enim mollit quis nostrud nulla quis non minim voluptate cillum sint tempor mollit. Culpa anim occaecat aliquip do. Aliquip velit minim irure nostrud commodo eiusmod consequat ipsum consectetur deserunt dolore. Pariatur dolor dolore consectetur dolor aliqua dolor dolor deserunt minim commodo.\n\nAd qui qui ex et irure eiusmod. Excepteur esse fugiat officia non ex excepteur minim sint voluptate in incididunt. Exercitation culpa laboris non consequat excepteur pariatur est consequat aliquip occaecat ullamco laborum culpa. Ut fugiat duis incididunt incididunt excepteur enim sunt in amet irure nulla. Commodo officia fugiat do nostrud adipisicing sint voluptate voluptate dolor laboris. Nisi id aliqua quis id ullamco reprehenderit enim elit in magna. Proident consectetur voluptate id mollit sint do ipsum id sint proident.\n\nLaboris mollit nulla culpa veniam est dolor fugiat id consequat nulla veniam enim enim. Ullamco sunt proident fugiat cillum labore nostrud incididunt exercitation esse. Labore aliqua est non consequat in excepteur ullamco cupidatat aute nostrud proident. Consectetur enim veniam eiusmod incididunt culpa qui ipsum ea elit non nostrud reprehenderit incididunt veniam. Sint amet Lorem ipsum et dolore pariatur anim consectetur.\n\nBest Regards,\nGraves Huber', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '7cba834c-3011-4897-be7d-ee43bbe69114', + type: 'mail', + from: { + avatar: 'images/avatars/male-11.jpg', + contact: 'Tucker Santiago ', + }, + to: 'me ', + date: new Date('Mon Sep 17 2018 14:41:42 GMT+0000 (UTC)').toISOString(), + subject: 'Ullamco qui ex eu ea officia labore incididunt', + content: + 'Dear Brian,\n\nNon tempor sint incididunt adipisicing cupidatat laboris elit incididunt ipsum magna. Voluptate labore cillum irure dolor eu est commodo nulla. Cupidatat aliquip reprehenderit proident duis labore aliquip ullamco dolor occaecat anim esse tempor enim dolore. Elit veniam minim cupidatat aute ea voluptate eu et labore amet eu tempor.\n\nExercitation et exercitation labore cillum reprehenderit eiusmod anim magna ex. Lorem aliqua est velit eu. Qui et ullamco adipisicing elit eiusmod aliquip exercitation laboris consequat esse. Sint velit deserunt est quis ad proident sit eiusmod commodo eiusmod Lorem. Est consequat cillum magna est. Sunt pariatur voluptate elit officia aute.\n\nConsectetur velit deserunt non enim exercitation esse irure aliqua cillum sint in officia Lorem esse. Adipisicing consequat anim magna exercitation mollit. Ipsum irure in culpa mollit cillum eiusmod sunt amet consectetur anim eiusmod ea.\n\nBest Regards,\nTucker Santiago', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: 'd0de071d-2d72-4e0f-b903-79ca6ade9dbd', + type: 'mail', + from: { + avatar: 'images/avatars/female-07.jpg', + contact: 'Becky Cain ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Nov 30 2018 09:14:15 GMT+0000 (UTC)').toISOString(), + subject: + 'Exercitation amet laborum officia nulla nulla adipisicing mollit culpa eiusmod irure deserunt voluptate laborum', + content: + 'Dear Brian,\n\nAd tempor veniam exercitation et occaecat do quis do cillum nulla mollit mollit nulla minim. Id sint do excepteur pariatur eu pariatur do sint ipsum ea. Enim in ex irure eu incididunt aliqua eu velit ipsum magna elit eu.\n\nCupidatat fugiat proident aliqua labore nostrud Lorem veniam tempor dolor exercitation. Aliqua magna pariatur exercitation voluptate do duis ea voluptate est culpa sint id. Irure labore esse adipisicing culpa ad velit consectetur. Sint mollit voluptate tempor exercitation fugiat consectetur cillum officia non dolor.\n\nIpsum amet esse duis duis est voluptate ipsum ipsum ipsum qui labore exercitation veniam. Proident sint incididunt ut sunt ut labore sunt ex. Ea enim velit qui elit non sit excepteur dolore eiusmod.\n\nKind Regards,\nBecky Cain', + attachments: [ + { + type: 'image/jpeg', + name: 'mystery-forest.jpg', + size: 15539, + preview: 'mystery-forest_preview.jpg', + downloadUrl: '', + }, + { + type: 'image/png', + name: 'lake-of-carezza.png', + size: 13071, + preview: 'lake-of-carrezza_preview.png', + downloadUrl: '', + }, + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + ], + starred: false, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'd39c93b9-10d3-426f-a205-0ee5b30cd983', + type: 'mail', + from: { + avatar: 'images/avatars/male-12.jpg', + contact: 'Miller Vazquez ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sun May 06 2018 19:35:14 GMT+0000 (UTC)').toISOString(), + subject: 'Ullamco eu nulla labore occaecat', + content: + 'Hey Brian,\n\nIrure reprehenderit enim anim ad ex officia qui. Cillum amet reprehenderit aliquip minim adipisicing. Sint sit tempor non nostrud esse adipisicing eu.\n\nLabore dolor sint minim enim officia voluptate. Eu esse est velit ipsum ullamco amet anim aliquip culpa sit laborum velit. Eiusmod est nulla dolor duis voluptate deserunt. Labore do qui amet laborum tempor anim. Do aliqua est anim consequat proident minim. Commodo eiusmod labore elit sunt irure labore proident non ipsum in eiusmod laboris sit. Ad nostrud cupidatat ea est veniam commodo culpa laboris mollit id.\n\nLabore aute fugiat commodo sint aliquip ullamco sint esse. Deserunt aliqua amet tempor Lorem pariatur. Eiusmod proident reprehenderit pariatur cupidatat. Ad consequat laboris nisi in ipsum nisi dolor et velit duis do ad. Ipsum minim reprehenderit quis incididunt culpa. Et laborum laborum quis in elit nisi proident cillum sit ad. Nisi ullamco aliquip elit nisi sint sunt enim est commodo aute aliquip cupidatat eiusmod adipisicing.\n\nBest Regards,\nMiller Vazquez', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '4e881b4f-bf47-472f-a1fe-f787a66d37dd', + type: 'mail', + from: { + avatar: 'images/avatars/male-13.jpg', + contact: 'Delgado Stevens ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Wed Jan 17 2018 09:56:25 GMT+0000 (UTC)').toISOString(), + subject: + 'Ut velit occaecat nostrud ullamco non excepteur velit ipsum ut', + content: + 'Hey Brian,\n\nEsse dolore exercitation id sint id eu dolor nisi. Irure consectetur aute eu ad aute velit et tempor ad eiusmod voluptate. Sit proident pariatur anim in culpa ut esse nostrud incididunt ullamco ut. Proident nostrud est voluptate sint nostrud dolore amet dolore culpa eiusmod enim voluptate in. Do qui voluptate ex aliqua ut.\n\nQui ullamco incididunt nulla adipisicing tempor aute commodo eu adipisicing. Tempor exercitation tempor adipisicing ipsum incididunt mollit commodo adipisicing. Cupidatat officia in ut duis non commodo ut.\n\nCulpa eiusmod mollit culpa nostrud ullamco irure. Est adipisicing ut irure Lorem esse. Deserunt deserunt non sit sit labore et minim fugiat irure Lorem et velit.\n\nKind Regards,\nDelgado Stevens', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '5dad8b60-8d98-4215-88b8-41158e167686', + type: 'mail', + from: { + avatar: 'images/avatars/female-08.jpg', + contact: 'Concepcion Cleveland ', + }, + to: 'me ', + date: new Date('Fri Aug 17 2018 04:41:26 GMT+0000 (UTC)').toISOString(), + subject: + 'Aliqua pariatur non tempor velit eu amet sit et proident Lorem', + content: + 'Hey Brian,\n\nPariatur anim aute excepteur consequat esse aliqua proident culpa duis duis veniam occaecat cupidatat eu. Lorem officia occaecat duis et exercitation mollit consectetur pariatur ut sit exercitation. Velit consectetur incididunt ad non nostrud dolor consequat esse deserunt. Nisi consectetur ea ut cupidatat ipsum. Sint cillum cupidatat elit laboris incididunt consectetur veniam fugiat sit voluptate officia.\n\nCillum est dolore et aliqua elit pariatur cupidatat eiusmod officia. Nisi officia velit commodo id anim qui commodo aliquip mollit. Quis magna sunt in duis laboris fugiat veniam. Proident consequat deserunt sunt amet officia veniam.\n\nIrure irure aliqua officia deserunt. Excepteur excepteur magna Lorem minim esse in sit. Magna elit laborum sunt magna labore eu eiusmod qui aliqua laborum.\n\nCheers!\nConcepcion Cleveland', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'eeccc250-8952-47e1-adff-31847289b4dd', + type: 'mail', + from: { + avatar: 'images/avatars/female-09.jpg', + contact: 'Robin Berger ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Apr 26 2018 20:55:18 GMT+0000 (UTC)').toISOString(), + subject: + 'Reprehenderit ipsum aute cupidatat qui exercitation dolore voluptate labore veniam consequat quis', + content: + 'Hello Brian,\n\nExercitation minim anim commodo eu deserunt voluptate consectetur. Sit enim excepteur ipsum ea esse labore fugiat nulla. Do est tempor duis in consectetur proident do consectetur. Cupidatat ex id voluptate fugiat aute Lorem elit tempor.\n\nMinim nulla occaecat mollit laboris ea et laboris velit. Velit ullamco quis esse veniam exercitation veniam quis nostrud qui officia commodo. Lorem excepteur magna officia in eu exercitation qui ad. Nisi velit qui ipsum anim veniam deserunt velit adipisicing tempor esse excepteur ipsum. Voluptate sit dolore irure deserunt dolor incididunt laboris tempor. Minim id deserunt ea duis labore incididunt est ullamco mollit pariatur sit duis commodo. Proident ipsum eiusmod ea excepteur.\n\nIrure adipisicing veniam dolor consequat sit incididunt ad cupidatat fugiat eiusmod aliqua id amet labore. Excepteur minim deserunt eiusmod sunt aliqua ipsum. Ullamco nostrud minim ullamco amet Lorem ut irure officia mollit duis enim. Laborum dolore dolor nulla consequat consequat nostrud velit eu deserunt aute deserunt est. Adipisicing laborum dolore pariatur ad tempor culpa exercitation consequat eu anim nulla magna. In est culpa tempor laborum voluptate.\n\nKind Regards,\nRobin Berger', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'e8b006ad-e140-4fea-bb32-cc346e66eb93', + type: 'mail', + from: { + avatar: 'images/avatars/female-10.jpg', + contact: 'Lynnette Burton ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Fri Nov 02 2018 16:15:50 GMT+0000 (UTC)').toISOString(), + subject: + 'Consectetur deserunt qui ex occaecat dolore officia cillum magna magna adipisicing elit magna velit', + content: + 'Hi Brian,\n\nNulla mollit excepteur ex officia anim cillum eiusmod. Fugiat ullamco ad eu elit Lorem eiusmod veniam ut ipsum cillum culpa. Dolore commodo ea quis labore qui irure velit duis nostrud aute dolore non amet dolor. Qui ullamco elit reprehenderit anim tempor aliquip mollit id pariatur voluptate cupidatat anim voluptate. Exercitation nostrud sint adipisicing ad consectetur. Culpa officia occaecat aute pariatur duis occaecat mollit ea deserunt ipsum. Adipisicing non ipsum sint quis mollit consectetur occaecat anim sint.\n\nOfficia ea excepteur elit non. Velit aute ad consequat deserunt labore culpa consectetur ullamco occaecat ullamco qui laborum labore. Magna consequat dolore proident incididunt adipisicing exercitation sint anim et laboris occaecat quis. Tempor do anim magna ullamco reprehenderit aliqua et laboris non nostrud sunt. Elit aliquip irure officia reprehenderit voluptate nisi officia ex. Quis mollit sit qui eiusmod veniam eu non. Eiusmod ullamco velit occaecat pariatur ea.\n\nCillum velit sit duis esse dolor do velit sit Lorem. Enim occaecat voluptate excepteur irure anim officia nisi culpa. Quis fugiat cupidatat veniam deserunt pariatur. Exercitation ad irure nulla cupidatat nisi nostrud Lorem proident veniam ullamco labore dolore velit. Reprehenderit eiusmod ea cillum sit anim nostrud proident consequat nostrud duis adipisicing.\n\nKind Regards,\nLynnette Burton', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'c6fc1d86-4a1f-4071-9c75-618fe2d853aa', + type: 'mail', + from: { + avatar: 'images/avatars/female-11.jpg', + contact: 'Carmella Rios ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Jul 05 2018 14:26:57 GMT+0000 (UTC)').toISOString(), + subject: + 'Proident dolore minim deserunt commodo elit aute laborum ullamco laboris anim consequat', + content: + 'Dear Brian,\n\nIn do ex sit amet fugiat duis eu non non duis id sit. Elit pariatur nostrud veniam Lorem aliquip tempor ipsum ut minim eiusmod ad. Sunt et veniam ad incididunt sint occaecat. Exercitation voluptate culpa ex enim. Excepteur exercitation aute consequat non culpa. Lorem ut consectetur commodo laboris esse exercitation laborum do ut duis sunt sint. Aliquip laboris fugiat officia irure consequat pariatur velit ea ut commodo.\n\nIpsum adipisicing proident cupidatat commodo nulla culpa nostrud ipsum voluptate occaecat sit. Occaecat anim cupidatat qui reprehenderit ex commodo dolore. Consectetur id magna esse amet do nulla esse aute velit id minim nostrud cupidatat. Nostrud dolore sunt labore sunt sit velit magna nulla. Sunt enim adipisicing sint deserunt enim veniam reprehenderit reprehenderit. Incididunt fugiat labore minim pariatur mollit ea veniam. Do voluptate id consequat qui ut aliqua qui dolore ex ea.\n\nDo esse duis culpa nisi in elit veniam exercitation consequat. Proident dolor laborum enim velit non nostrud. In reprehenderit proident enim sint tempor cupidatat enim fugiat. Duis laboris officia sit in sunt sunt sunt non. Quis mollit veniam cupidatat non enim Lorem aliquip et sit fugiat eiusmod cillum ad exercitation.\n\nCheers!\nCarmella Rios', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: 'c062a90f-52e9-4102-8def-1f8f9813deb4', + type: 'mail', + from: { + avatar: 'images/avatars/male-14.jpg', + contact: 'Molina Pace ', + }, + to: 'me ', + date: new Date('Mon Sep 17 2018 21:45:31 GMT+0000 (UTC)').toISOString(), + subject: 'Dolor anim non labore est aliquip sunt pariatur', + content: + 'Hi Brian,\n\nNon fugiat eu aute nulla deserunt Lorem id fugiat consectetur duis ut reprehenderit nostrud. Laboris culpa id nulla duis id proident. Eiusmod consequat commodo aute est deserunt aliquip esse aute qui aute et nostrud culpa.\n\nCillum minim reprehenderit sit nulla aliqua adipisicing deserunt non sit excepteur fugiat velit et. Amet excepteur non ipsum voluptate dolore irure. Laboris sit sunt id mollit et in nulla eiusmod duis ut tempor ea est. Dolor est laborum ipsum labore sint aliquip est minim ex. Aliqua cillum qui consectetur amet elit nostrud quis duis. Dolore consequat laborum laboris ullamco qui do cupidatat sunt deserunt ex elit cillum.\n\nVeniam exercitation eiusmod exercitation cupidatat sunt incididunt. Labore occaecat eiusmod sint consectetur eiusmod sunt quis ad Lorem ex. Ipsum labore ipsum nulla Lorem incididunt proident. Sit officia ut nostrud nisi ipsum adipisicing qui. Non nulla commodo nostrud aliqua Lorem consequat ea do dolore nisi veniam veniam occaecat. Tempor amet ex eiusmod irure aliquip minim. Laborum officia fugiat nisi magna.\n\nBest Regards,\nMolina Pace', + attachments: [ + { + type: 'image/jpeg', + name: 'birds-eye-sydney.jpg', + size: 14294, + preview: 'birds-eye-sydney_preview.jpg', + downloadUrl: '', + }, + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + ], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '545d12a6-e0f3-464d-af45-618163933a71', + type: 'mail', + from: { + avatar: 'images/avatars/female-12.jpg', + contact: 'Olga Osborn ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Thu Sep 27 2018 13:45:14 GMT+0000 (UTC)').toISOString(), + subject: + 'Elit tempor enim nisi cillum cillum esse do magna exercitation minim', + content: + 'Dear Brian,\n\nEu voluptate dolore anim deserunt nostrud pariatur voluptate exercitation et et veniam fugiat sint consequat. Aute esse pariatur ullamco reprehenderit velit pariatur mollit sunt enim culpa qui anim sit officia. Labore minim elit commodo sunt fugiat in sint adipisicing aute incididunt adipisicing in officia esse. Sit enim eu irure ullamco ea pariatur dolore exercitation labore excepteur laborum exercitation dolore. Magna dolor Lorem fugiat eiusmod consectetur sit.\n\nExcepteur sunt officia minim in do esse. Nostrud ullamco dolore esse laborum aliquip sit consequat. Excepteur irure occaecat cupidatat cupidatat deserunt esse deserunt voluptate non labore culpa. Sit voluptate non eu sit. Velit pariatur esse et ex in laboris cillum Lorem tempor consequat. Magna consequat nostrud duis minim. In ad irure commodo deserunt incididunt duis sit quis voluptate ullamco laboris laborum commodo.\n\nIn duis eiusmod proident excepteur. Magna proident do ad est amet pariatur sint cupidatat ullamco velit cillum. Ea esse proident non culpa do in minim eiusmod.\n\nCheers!\nOlga Osborn', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '23456530-2cd4-4558-95d0-6311c2ee2ee8', + type: 'mail', + from: { + avatar: 'images/avatars/female-13.jpg', + contact: 'Brooke Petersen ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sun Jul 22 2018 14:49:16 GMT+0000 (UTC)').toISOString(), + subject: + 'Anim laboris aliquip excepteur consectetur eu enim sunt velit qui deserunt', + content: + 'Hello Brian,\n\nConsequat velit voluptate exercitation sint anim laboris. Consectetur dolor sunt veniam incididunt ad laboris proident tempor voluptate enim excepteur. Nostrud eu id tempor cupidatat. Deserunt ullamco consequat esse et. Dolore qui cupidatat commodo ea nisi tempor velit sit aliquip amet.\n\nMagna fugiat cupidatat mollit mollit. Consectetur consequat occaecat pariatur commodo quis labore est cillum voluptate culpa tempor elit incididunt. Voluptate anim est eiusmod voluptate ipsum commodo do et elit. Aute pariatur adipisicing eu laboris proident Lorem qui enim magna adipisicing deserunt pariatur. Fugiat eiusmod occaecat dolor tempor sunt exercitation est amet mollit est. Est in duis adipisicing nostrud aute voluptate quis in fugiat veniam reprehenderit.\n\nIpsum id deserunt ex non nisi nostrud enim pariatur nulla. In labore qui esse veniam ut. Est id ut pariatur esse nulla dolore aliqua ad aliqua fugiat. Ad incididunt amet culpa labore enim proident tempor. Aliquip non dolore sunt eu deserunt tempor anim qui dolore quis. Est sunt enim ipsum aliqua.\n\nKind Regards,\nBrooke Petersen', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'affeecf6-e3c5-4377-8070-96f5ed9c6500', + type: 'mail', + from: { + avatar: 'images/avatars/male-15.jpg', + contact: 'Estes Walter ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Jul 17 2018 03:52:54 GMT+0000 (UTC)').toISOString(), + subject: 'Reprehenderit ad do quis ut fugiat proident labore', + content: + 'Hello Brian,\n\nFugiat labore incididunt aute sint id laboris nisi eiusmod reprehenderit. Sint sint Lorem aute cillum velit occaecat sit quis laboris ipsum laborum. Ex ipsum ea proident duis ex nostrud dolore exercitation nostrud ullamco cupidatat irure dolor. In aliqua occaecat commodo irure dolore. Nisi laborum anim cillum aute adipisicing labore fugiat velit officia cupidatat aliquip voluptate veniam. Aute incididunt consequat est id commodo elit occaecat ea Lorem deserunt est.\n\nPariatur deserunt sunt excepteur nisi ex. Enim consequat esse in deserunt ut. Cillum incididunt exercitation fugiat reprehenderit amet dolor nulla irure id quis.\n\nEnim id incididunt labore commodo voluptate. Non sint sint in eu anim dolor aliquip ullamco occaecat esse id consectetur cupidatat. Mollit aute nisi et fugiat consequat.\n\nKind Regards,\nEstes Walter', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'cb00d05c-6660-4dbb-a794-f22cff93f001', + type: 'mail', + from: { + avatar: 'images/avatars/male-16.jpg', + contact: 'Holman Oconnor ', + }, + to: 'me ', + date: new Date('Sat Jan 13 2018 22:30:44 GMT+0000 (UTC)').toISOString(), + subject: + 'Duis dolore eu sint anim ipsum commodo esse cillum ipsum culpa commodo', + content: + 'Dear Brian,\n\nDolore amet sunt ullamco enim quis tempor enim pariatur nostrud id nulla adipisicing. Reprehenderit amet ex ullamco pariatur proident et amet consequat ipsum sit ut. Laboris fugiat nulla consequat nulla qui tempor dolore dolore minim nisi. Excepteur non est pariatur cupidatat adipisicing veniam ea cillum ex duis laborum ullamco.\n\nDo quis cupidatat adipisicing sint minim veniam velit amet nulla nisi tempor voluptate nulla adipisicing. Ex dolor adipisicing dolor ad cupidatat consectetur cillum ex. Non velit laborum fugiat est fugiat officia esse ullamco. Ut amet sit mollit dolor. Sit ipsum reprehenderit consectetur excepteur. Enim ad aliqua consequat ipsum labore laborum culpa aliqua dolor voluptate.\n\nVelit cupidatat labore sint id excepteur anim qui do do non. Esse tempor aute nisi aliqua velit ad elit nulla pariatur aliqua consectetur. Proident pariatur mollit cupidatat ullamco est velit ullamco dolore. Ipsum sunt reprehenderit consequat eu dolore nisi nostrud ipsum cillum. Quis non esse fugiat ipsum ad laboris aliquip eiusmod deserunt qui ipsum aliqua consequat duis. Ad nisi sunt sint ut officia adipisicing labore voluptate commodo ullamco exercitation.\n\nKind Regards,\nHolman Oconnor', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '7bd27a67-df7c-4a2d-8042-1fb8a690d98e', + type: 'mail', + from: { + avatar: 'images/avatars/male-17.jpg', + contact: 'Oneill Irwin ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sat Nov 17 2018 08:17:34 GMT+0000 (UTC)').toISOString(), + subject: + 'Eu in ut dolor amet consequat aute esse non fugiat minim cillum sunt aliquip cillum', + content: + 'Dear Brian,\n\nNostrud sint ea laboris excepteur dolor nisi mollit dolor voluptate irure ex. Laboris adipisicing id ad minim minim magna nostrud nulla quis nulla tempor. Qui incididunt velit qui et proident eu in tempor in aliqua reprehenderit nostrud aute nulla. Nisi officia fugiat officia irure cillum qui adipisicing qui. Culpa duis sunt aute nostrud elit esse sit ullamco commodo ex.\n\nVeniam Lorem est minim sint ut aliqua ut est exercitation ad aliquip ullamco in culpa. Amet qui do adipisicing magna eu reprehenderit anim enim. Ipsum consequat ut eiusmod irure amet commodo aliqua sint aliquip non nulla. Irure excepteur tempor in ullamco sit culpa labore dolor enim sit. Nostrud eiusmod ex nulla exercitation est esse velit dolore aliqua eiusmod sit. Elit dolore id proident fugiat culpa anim ea Lorem eiusmod aliqua ex culpa in tempor.\n\nDeserunt officia id excepteur esse nisi elit labore irure. Et sint dolor ex incididunt ipsum dolore in mollit tempor. Qui cillum consequat laboris non culpa laborum amet cillum mollit laboris anim duis pariatur consequat. Ipsum fugiat cupidatat proident magna nisi consectetur adipisicing minim labore. Officia consequat quis labore sunt.\n\nKind Regards,\nOneill Irwin', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: 'ccfb3a90-e18c-4645-8c00-4357d9bcd321', + type: 'mail', + from: { + avatar: 'images/avatars/female-14.jpg', + contact: 'Marcie Morgan ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Nov 20 2018 05:55:32 GMT+0000 (UTC)').toISOString(), + subject: + 'Magna velit cillum dolor reprehenderit aliqua ut aute nisi in sunt Lorem laboris elit do', + content: + 'Hi Brian,\n\nCommodo id eu mollit dolor laboris incididunt exercitation labore duis eu mollit labore labore labore. Cupidatat fugiat aute non consequat eiusmod in Lorem. Consequat officia ullamco minim aliquip aliqua.\n\nIrure elit ipsum minim ad Lorem. In amet Lorem aute minim id consequat nulla. Tempor ipsum incididunt occaecat sit ipsum adipisicing pariatur magna aliquip adipisicing quis id pariatur est. Tempor sit dolor aute do aliqua. Est cillum adipisicing ut aliquip adipisicing est nostrud tempor tempor culpa laboris occaecat. Ipsum culpa veniam sit aliqua ad culpa Lorem esse pariatur incididunt adipisicing irure ea. Sunt nostrud do quis tempor reprehenderit anim dolore mollit fugiat nisi.\n\nPariatur nostrud id occaecat dolor sunt. Ipsum dolore ex minim ex tempor sint ad elit eiusmod ipsum veniam. Aliquip occaecat nisi sunt aliquip id. Reprehenderit aliquip nisi ea culpa eu commodo Lorem consectetur.\n\nKind Regards,\nMarcie Morgan', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'a18b1961-ad32-4d00-984f-afef8ee0f4e9', + type: 'mail', + from: { + avatar: 'images/avatars/male-18.jpg', + contact: 'Crane Trevino ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sat Nov 03 2018 01:52:28 GMT+0000 (UTC)').toISOString(), + subject: 'Tempor consectetur officia excepteur culpa', + content: + 'Dear Brian,\n\nExercitation in non sint adipisicing reprehenderit eu est aute aute quis Lorem. Magna labore nisi amet magna do in. Eiusmod fugiat mollit mollit minim aute. Voluptate qui sunt eiusmod aliquip pariatur consectetur et culpa laborum dolore. Exercitation ad incididunt exercitation voluptate sit qui eu incididunt sit.\n\nVoluptate cillum qui proident dolore tempor excepteur aute magna esse ex est culpa in. Officia officia quis veniam sunt irure eu. Voluptate ullamco velit culpa laboris anim commodo esse sunt minim esse nostrud ea. Est eiusmod commodo occaecat anim sint exercitation. Sunt irure nisi est sit excepteur aute amet. Non labore ullamco tempor nostrud nostrud ea do nostrud Lorem veniam in. Dolor est esse duis aute.\n\nEnim fugiat sunt et ut officia fugiat reprehenderit. Id cupidatat qui occaecat proident incididunt deserunt nisi magna enim dolore. Dolor aute anim ex tempor nisi ex minim sint reprehenderit ex ullamco ullamco culpa ipsum. Voluptate occaecat esse consequat non aliqua proident. Deserunt exercitation Lorem ea nisi consequat et culpa pariatur. Incididunt commodo deserunt dolore irure ea sint ipsum ad voluptate.\n\nBest Regards,\nCrane Trevino', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '3aaa5e3f-b8b5-47fc-9967-5f65dd8c7251', + type: 'mail', + from: { + avatar: 'images/avatars/female-15.jpg', + contact: 'Kristine Wiggins ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Jan 09 2018 13:55:10 GMT+0000 (UTC)').toISOString(), + subject: + 'Magna aute enim magna aliqua aliquip enim elit eiusmod nulla nostrud', + content: + 'Hi Brian,\n\nCulpa incididunt qui nulla velit consectetur. Exercitation ut voluptate proident commodo non deserunt. Consectetur anim aute sunt aliquip fugiat laborum tempor exercitation duis sint excepteur ullamco culpa consequat. Aliqua ex quis pariatur excepteur commodo adipisicing ut anim et. Duis ex sit ex nulla proident est consequat aliquip. Quis exercitation labore veniam anim sit irure laborum occaecat laborum labore cillum sunt nulla. Exercitation laborum sunt consequat aliqua.\n\nLabore fugiat ullamco quis incididunt quis duis consectetur aute incididunt cupidatat cupidatat deserunt. Cillum fugiat ex minim tempor consectetur duis labore reprehenderit excepteur enim anim qui. Reprehenderit pariatur aliqua mollit in amet id. Duis anim nostrud incididunt adipisicing incididunt velit minim tempor adipisicing est elit ipsum duis.\n\nFugiat nostrud ad enim officia est. Voluptate velit in pariatur cupidatat irure dolor eiusmod voluptate irure voluptate ad reprehenderit est. Esse aute aliquip aute minim amet pariatur minim tempor nostrud consectetur. Sunt reprehenderit excepteur occaecat ea reprehenderit eiusmod duis cupidatat sunt nulla fugiat et velit elit. Do ut tempor cillum nisi. Magna sint do et mollit cupidatat ad culpa voluptate.\n\nKind Regards,\nKristine Wiggins', + attachments: [ + { + type: 'application/pdf', + name: 'account-details.pdf', + size: 127844, + preview: 'pdf', + downloadUrl: '', + }, + { + type: 'image/jpeg', + name: 'mystery-forest.jpg', + size: 15539, + preview: 'mystery-forest_preview.jpg', + downloadUrl: '', + }, + { + type: 'image/jpeg', + name: 'birds-eye-sydney.jpg', + size: 14294, + preview: 'birds-eye-sydney_preview.jpg', + downloadUrl: '', + }, + ], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '0f72d2d0-bea4-4c0f-ace0-0be9f14c37f1', + type: 'mail', + from: { + avatar: 'images/avatars/female-16.jpg', + contact: 'Terrie Carney ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Jan 23 2018 00:03:55 GMT+0000 (UTC)').toISOString(), + subject: + 'Laboris in incididunt labore labore deserunt deserunt nostrud mollit voluptate non ex', + content: + 'Hello Brian,\n\nReprehenderit veniam fugiat sunt in nulla anim commodo magna ex nulla. Mollit nostrud eiusmod aute veniam. Sint do cupidatat velit sit amet.\n\nUllamco elit anim veniam culpa veniam velit. Nisi aute esse consectetur ea occaecat ea laboris eu. Velit proident quis mollit nulla mollit dolor ad commodo. Non deserunt ipsum id dolor est ad consectetur sunt commodo adipisicing in irure.\n\nCupidatat consequat officia adipisicing amet esse veniam veniam elit veniam sint nulla quis qui commodo. Ipsum nisi deserunt pariatur nostrud in. Sint duis pariatur esse do duis proident consequat ullamco excepteur mollit nulla veniam non. Reprehenderit incididunt ipsum duis dolor nulla fugiat fugiat culpa laboris velit sint.\n\nKind Regards,\nTerrie Carney', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: 'f825c5a3-2be8-4d48-9c4e-da60ff0e63f3', + type: 'mail', + from: { + avatar: 'images/avatars/male-19.jpg', + contact: 'Goff Jennings ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Tue Aug 07 2018 05:20:39 GMT+0000 (UTC)').toISOString(), + subject: + 'Labore sint dolor nulla nostrud commodo amet nisi mollit commodo eiusmod duis quis irure non', + content: + 'Dear Brian,\n\nNisi sit ut in do aliqua nostrud consectetur incididunt. Non et pariatur nulla mollit aute aliquip amet minim irure tempor eu id ipsum. Velit sunt tempor proident voluptate ad reprehenderit. Dolor consectetur est in nulla. Reprehenderit incididunt magna deserunt mollit officia non aliqua. Elit est dolore ea Lorem velit ipsum occaecat cupidatat. Mollit magna laborum qui sit sunt mollit amet.\n\nDuis excepteur labore laboris adipisicing culpa culpa eiusmod et velit aliquip velit. Proident tempor in excepteur minim irure duis ex in non est. Labore minim sunt culpa enim tempor labore ea adipisicing nulla elit magna. Fugiat enim ex voluptate officia pariatur pariatur ipsum eu in. Veniam commodo occaecat laborum excepteur nisi Lorem.\n\nExcepteur adipisicing amet ea commodo dolor nisi labore deserunt adipisicing pariatur. Pariatur magna et esse id occaecat minim minim. Labore cupidatat tempor deserunt reprehenderit anim duis magna laborum excepteur aliquip consectetur.\n\nBest Regards,\nGoff Jennings', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'e6dc9600-a3ab-4571-b2f2-ed00ee08e163', + type: 'mail', + from: { + avatar: 'images/avatars/male-20.jpg', + contact: 'Browning Sanchez ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Fri Mar 16 2018 20:31:08 GMT+0000 (UTC)').toISOString(), + subject: + 'Mollit cupidatat commodo consectetur duis ea elit est sint sunt ea qui nostrud incididunt', + content: + 'Hey Brian,\n\nVelit ut elit ex voluptate nisi nostrud sunt pariatur dolore est dolor deserunt sint nostrud. Aute magna ipsum cillum cillum tempor voluptate cupidatat sunt eiusmod officia sit. Aliqua adipisicing officia adipisicing dolore id nulla nulla irure non enim esse anim. Tempor occaecat excepteur duis ex aliquip eu reprehenderit labore ea. Adipisicing anim amet culpa culpa cillum elit cupidatat consequat laboris.\n\nEx dolore fugiat incididunt deserunt deserunt quis elit ipsum. Exercitation dolore dolore deserunt eu voluptate deserunt non id duis incididunt. Dolor proident quis enim cillum fugiat. Ex nisi pariatur aliqua exercitation. Incididunt laborum pariatur deserunt anim laboris sint consequat aliqua nostrud sint. Elit tempor laboris do tempor eu minim sunt proident.\n\nAmet aute esse minim qui sit pariatur aliquip laborum. Irure nulla sit laboris dolor reprehenderit veniam occaecat non commodo do qui. Eiusmod pariatur dolor consectetur qui quis occaecat. Et consectetur occaecat nulla elit officia nostrud. Est aute est nisi dolor mollit sunt et aliqua aliqua nulla labore cupidatat. Do pariatur aliquip cillum ullamco. Nostrud tempor consectetur eu nisi incididunt in voluptate est.\n\nKind Regards,\nBrowning Sanchez', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '0f22fedf-ea89-414e-91a4-0df0d9501ef2', + type: 'mail', + from: { + avatar: 'images/avatars/male-01.jpg', + contact: 'Carey Lyons ', + }, + to: 'me ', + date: new Date('Tue May 01 2018 07:56:59 GMT+0000 (UTC)').toISOString(), + subject: 'Laboris esse ipsum esse eu do ipsum do incididunt', + content: + 'Hello Brian,\n\nIpsum elit ut magna occaecat dolor sint reprehenderit eu incididunt sunt irure esse mollit. Sit fugiat amet laborum ullamco sit laborum Lorem irure minim ut. Labore aliqua dolore minim elit consequat sit. Labore mollit esse ad magna voluptate anim pariatur. Irure enim excepteur adipisicing cillum minim culpa elit nostrud consectetur quis laborum velit. Ea eiusmod aliqua ipsum ad tempor veniam fugiat elit.\n\nDolor mollit adipisicing ut duis cillum proident id sunt non sit cillum. Sit aliqua elit aute tempor cupidatat esse mollit do deserunt cillum velit irure cillum. Ea aliqua Lorem minim cupidatat elit Lorem.\n\nEu deserunt nostrud Lorem reprehenderit sit veniam consectetur proident. Duis elit duis excepteur sit proident est ut est cillum. Sit tempor aliqua qui laborum eu cillum laborum consequat adipisicing sit exercitation. Anim non do consequat duis pariatur. Velit excepteur magna enim tempor occaecat consequat exercitation laborum deserunt.\n\nKind Regards,\nCarey Lyons', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'd942f99b-8925-49f0-b75b-2c48b714b1cf', + type: 'mail', + from: { + avatar: 'images/avatars/male-02.jpg', + contact: 'Hendrix Goodwin ', + }, + to: 'me ', + date: new Date('Mon Jan 22 2018 19:04:29 GMT+0000 (UTC)').toISOString(), + subject: 'Magna consectetur occaecat excepteur elit', + content: + 'Hello Brian,\n\nSunt consequat elit aliquip sit nulla ad. Voluptate elit qui magna ipsum culpa pariatur laboris nisi sit laboris. Mollit eiusmod ut elit est aliquip nulla ea laborum. Irure ipsum officia cillum labore occaecat esse consequat ut culpa et ut.\n\nAliquip aliquip veniam aute velit aliquip culpa cillum. Eu culpa pariatur in exercitation est nostrud duis quis voluptate. Anim pariatur ipsum aliquip proident et enim veniam duis velit adipisicing id ad exercitation commodo. Sit commodo qui reprehenderit et elit officia in aliquip amet occaecat. Nulla aute officia duis cupidatat cillum. Commodo amet consequat qui ipsum nisi nulla veniam laborum. Et excepteur est irure non officia ipsum sunt fugiat exercitation eu laboris sunt.\n\nElit reprehenderit aute consectetur eiusmod sit pariatur elit fugiat irure id et. In in dolore sunt magna cillum excepteur minim aute. Lorem sint occaecat elit est sint ut ea eiusmod anim esse cillum anim enim. Officia sint velit qui minim veniam ut nisi reprehenderit occaecat laborum qui. Elit eiusmod commodo dolor sunt incididunt labore.\n\nBest Regards,\nHendrix Goodwin', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '036c24e8-d8bc-4f0e-9a72-6fa884d69bb3', + type: 'mail', + from: { + avatar: 'images/avatars/female-17.jpg', + contact: 'Leticia Fulton ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sat Mar 31 2018 05:44:48 GMT+0000 (UTC)').toISOString(), + subject: + 'Est nostrud labore excepteur quis consectetur proident cupidatat', + content: + 'Hello Brian,\n\nOfficia incididunt sint est non aliquip eu deserunt sunt ad minim aliqua excepteur. Cillum dolor nostrud magna sunt nulla aute ut esse dolore magna eu. Dolore minim non dolor aliquip reprehenderit excepteur irure dolore anim incididunt sit. Nulla commodo pariatur consectetur sit reprehenderit amet consectetur duis.\n\nCupidatat tempor commodo aliqua sunt incididunt. Occaecat occaecat eu officia aliqua in exercitation sint commodo aute aliquip laborum consectetur enim. Voluptate do aute irure ullamco. Qui consectetur id aliqua laborum incididunt cupidatat proident ea irure mollit minim.\n\nDolore reprehenderit occaecat enim eu veniam tempor dolor. Aliquip proident tempor aute nostrud ut. Eiusmod consectetur qui mollit ut ut ullamco aliquip exercitation quis dolore irure labore. Nisi officia aliquip pariatur Lorem velit ex cupidatat cillum consequat. Elit ea sunt reprehenderit do minim cillum. Aute irure ad velit quis et adipisicing esse reprehenderit et quis voluptate. Aliquip reprehenderit duis eiusmod eiusmod aliqua mollit amet id cillum deserunt.\n\nCheers!\nLeticia Fulton', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '3dac4463-73aa-4bd4-a3d8-662ce38635cc', + type: 'mail', + from: { + avatar: 'images/avatars/female-18.jpg', + contact: 'Carmen Shannon ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Jun 15 2018 23:26:09 GMT+0000 (UTC)').toISOString(), + subject: 'Aute est laboris laborum consectetur cupidatat', + content: + 'Hey Brian,\n\nEsse dolore laboris enim quis. Ullamco dolor exercitation nostrud occaecat in et ad Lorem sunt nisi. Ipsum quis dolor fugiat ex eu. Consequat voluptate elit ut exercitation enim sint aliqua qui id est in eu adipisicing veniam. Deserunt est occaecat sit irure aute. Anim veniam cupidatat exercitation labore duis pariatur velit est exercitation dolore ad. Pariatur non adipisicing et nulla sit.\n\nIncididunt dolor pariatur est aute ad. Non aliqua qui excepteur cillum enim. Magna proident incididunt eu dolor non ut. Eiusmod Lorem tempor laborum amet ex.\n\nExcepteur quis duis cupidatat ea cupidatat magna irure ad exercitation eiusmod. Quis magna minim nulla ullamco. Sit dolor ipsum tempor laboris eiusmod deserunt ex. Est incididunt culpa commodo ad sunt cillum eiusmod labore nisi nulla ea sit anim incididunt. In labore id sint ipsum id nulla ad aliqua mollit minim occaecat. Velit do velit nostrud nostrud dolor esse consequat velit ullamco in cupidatat. Amet culpa fugiat Lorem nisi tempor labore magna reprehenderit aliquip elit et esse fugiat.\n\nKind Regards,\nCarmen Shannon', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '6bb27e81-ee53-4db3-acc7-bd1267cd475d', + type: 'mail', + from: { + avatar: 'images/avatars/female-19.jpg', + contact: 'Hattie Snow ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Jun 08 2018 01:23:42 GMT+0000 (UTC)').toISOString(), + subject: + 'Officia exercitation exercitation ad exercitation ea ut ullamco', + content: + 'Hello Brian,\n\nIncididunt aute pariatur quis reprehenderit tempor occaecat laborum nostrud labore sunt minim non eiusmod incididunt. Ipsum cupidatat qui reprehenderit ex enim irure. Eiusmod sunt proident Lorem veniam non magna dolore eu laboris nostrud quis pariatur. Velit do eu commodo tempor laboris excepteur in. Laborum mollit dolor aliquip enim sunt cillum minim. Dolor elit ipsum proident adipisicing consectetur aliquip nisi proident eiusmod Lorem adipisicing aliqua velit ea.\n\nDo adipisicing incididunt proident Lorem ullamco. Cupidatat fugiat et minim elit deserunt est. Occaecat laboris cillum elit aute cupidatat reprehenderit consequat est est ea occaecat sit consequat labore. Enim proident consectetur culpa anim est culpa nulla nostrud esse proident officia ut dolore ipsum. Do qui sunt id quis Lorem officia anim fugiat occaecat ut.\n\nEsse incididunt excepteur adipisicing fugiat deserunt sint Lorem culpa excepteur tempor ullamco qui. Non aliquip ullamco Lorem do. Ex enim elit minim reprehenderit in qui aliqua qui laborum.\n\nKind Regards,\nHattie Snow', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'bfac8e5d-6487-4747-b827-67179ac5c206', + type: 'mail', + from: { + avatar: 'images/avatars/female-20.jpg', + contact: 'Brandi Bradley ', + }, + to: 'me ', + date: new Date('Sat Nov 17 2018 10:51:44 GMT+0000 (UTC)').toISOString(), + subject: + 'Eiusmod nulla incididunt nostrud est mollit quis velit in non irure elit consectetur commodo irure', + content: + 'Hey Brian,\n\nOfficia ad enim aliqua ex labore nisi. Commodo cillum non occaecat laboris. Irure eu ut voluptate officia excepteur.\n\nNostrud ad proident qui cupidatat exercitation labore occaecat in. Aliquip culpa veniam magna eiusmod proident irure reprehenderit pariatur adipisicing velit. Aliqua non labore tempor irure do duis ut voluptate.\n\nNon sit dolore voluptate sint ullamco proident enim non do dolor deserunt nisi velit. Quis pariatur esse sunt quis voluptate ut minim proident officia exercitation ipsum ipsum cillum. Duis non nostrud ullamco excepteur occaecat. Deserunt sit sint quis et ad. Nisi enim excepteur magna laboris occaecat laborum non esse sit enim mollit. Et elit eiusmod eiusmod Lorem ex qui elit adipisicing proident aute eu.\n\nBest Regards,\nBrandi Bradley', + attachments: [ + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + { + type: 'image/png', + name: 'yosemite-national-park.png', + size: 14242, + preview: 'yosemite-national-park_preview.png', + downloadUrl: '', + }, + ], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: '81b49a0a-e934-422a-81a8-8506d6f24e0e', + type: 'mail', + from: { + avatar: 'images/avatars/female-01.jpg', + contact: 'Patsy Potter ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue May 15 2018 15:37:38 GMT+0000 (UTC)').toISOString(), + subject: + 'Ullamco fugiat fugiat non occaecat proident exercitation proident Lorem adipisicing commodo fugiat', + content: + 'Hello Brian,\n\nAute in culpa nulla aliqua laboris adipisicing in sit laborum. Enim exercitation duis qui ullamco. Ullamco eiusmod deserunt cillum nisi nulla nostrud voluptate fugiat non nulla. Tempor sint consequat in nostrud cupidatat exercitation aliqua Lorem. Fugiat officia excepteur consequat id cillum amet consectetur mollit nostrud in ex aliquip. Velit ut cupidatat excepteur deserunt.\n\nSit culpa eu dolor Lorem ipsum anim dolor proident. Cupidatat qui laboris incididunt Lorem cillum anim dolore ad ipsum ullamco deserunt aliquip exercitation. Nostrud magna fugiat aliquip veniam cupidatat cupidatat fugiat voluptate consectetur irure minim officia officia. Tempor commodo tempor sint amet. Ex sint adipisicing fugiat excepteur do ad elit esse commodo duis et. Ullamco irure laborum sint duis duis irure officia culpa non Lorem est deserunt exercitation.\n\nDolor ullamco fugiat eu cupidatat consequat exercitation magna. In ad aute aliquip eu laboris adipisicing proident ad. Eu aliquip enim cillum aliqua.\n\nKind Regards,\nPatsy Potter', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: '8355b50a-f347-4177-8cef-6410c0aa46d1', + type: 'mail', + from: { + avatar: 'images/avatars/female-02.jpg', + contact: 'Kathleen Cox ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sun Aug 26 2018 04:47:12 GMT+0000 (UTC)').toISOString(), + subject: + 'Est fugiat reprehenderit cupidatat sunt velit aliquip reprehenderit exercitation', + content: + 'Hey Brian,\n\nReprehenderit elit do qui ut occaecat veniam. Laboris culpa cupidatat irure ipsum ea cupidatat. Occaecat ea nisi cillum eiusmod. Excepteur dolore ut commodo magna consequat laboris aliquip pariatur reprehenderit laboris. Velit ullamco ipsum ut excepteur enim ipsum consequat reprehenderit eiusmod. Occaecat enim exercitation ipsum nulla dolor anim irure sint dolor do aliquip. Eu tempor sunt non pariatur ut anim eu.\n\nDeserunt cupidatat elit sit cillum qui ut velit ea dolor id sint. Laborum excepteur commodo sit duis. Sit sunt proident laborum ex deserunt cupidatat aliquip tempor id qui deserunt est deserunt et. Qui voluptate veniam nostrud deserunt ullamco nisi occaecat cillum aliquip ullamco. Duis labore sunt ad sunt cillum veniam fugiat deserunt commodo Lorem fugiat et. Eu laborum enim culpa duis esse tempor ex ex. Occaecat cupidatat est reprehenderit cupidatat fugiat amet dolor anim eiusmod.\n\nDolore est occaecat anim aute adipisicing do magna ea aute duis dolore nisi. Irure veniam dolore tempor sint. Irure ullamco aliqua id nisi elit Lorem amet do tempor. Irure aute consectetur dolore nisi nisi excepteur et labore fugiat excepteur duis adipisicing.\n\nKind Regards,\nKathleen Cox', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [], + }, + { + id: '6d2bfbce-465b-4e8f-a79e-cd13ab8571c6', + type: 'mail', + from: { + avatar: 'images/avatars/female-03.jpg', + contact: 'Kristina Ramirez ', + }, + to: 'me ', + date: new Date('Tue Mar 06 2018 16:13:56 GMT+0000 (UTC)').toISOString(), + subject: + 'Ea eu cupidatat voluptate magna et Lorem veniam aute ipsum consectetur nisi voluptate', + content: + 'Dear Brian,\n\nVoluptate esse cillum dolor aliqua. Qui aliqua consectetur tempor irure dolor sunt excepteur eu. Aliqua incididunt velit id minim consequat.\n\nLorem cupidatat aliqua enim fugiat ex aliqua fugiat do ut sint eiusmod. Ex Lorem incididunt velit laboris exercitation aliqua commodo est velit nisi excepteur aute dolor eu. Ad culpa excepteur non laboris occaecat aute sunt ea nostrud ut exercitation fugiat laboris. Ad eiusmod in culpa cupidatat sit pariatur deserunt velit velit elit aliqua duis eiusmod enim.\n\nFugiat ut proident consectetur aliquip consequat sunt ipsum adipisicing. Nisi velit eiusmod sunt voluptate do ea voluptate esse veniam deserunt consectetur Lorem laboris labore. Consectetur aute quis id nisi cillum magna elit veniam fugiat elit aliqua. Mollit aute laborum incididunt sit voluptate consectetur magna do do in duis sunt non culpa.\n\nKind Regards,\nKristina Ramirez', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '7c004a19-4506-48ef-93ab-f16381302e3b', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'd8815854-8726-4280-a5bf-eafd40b3972a', + type: 'mail', + from: { + avatar: 'images/avatars/male-03.jpg', + contact: 'Mays Glass ', + }, + to: 'me ', + date: new Date('Thu Jan 04 2018 11:10:36 GMT+0000 (UTC)').toISOString(), + subject: + 'Culpa ex pariatur aliqua reprehenderit do occaecat nulla ipsum culpa adipisicing', + content: + 'Hello Brian,\n\nQuis qui elit eiusmod sint adipisicing in. Adipisicing ipsum reprehenderit id tempor ut. Amet reprehenderit mollit commodo proident nulla velit aliqua ut labore ullamco ea reprehenderit proident deserunt. Consequat deserunt laborum consectetur ea aliquip. Lorem est cillum esse esse consequat sunt enim in deserunt velit. Consectetur velit sunt dolore fugiat eu dolor occaecat occaecat consequat et adipisicing ex ullamco. Officia labore esse esse ipsum ex laborum irure est id veniam aliqua sunt do.\n\nVeniam aute mollit elit duis. Voluptate veniam fugiat occaecat culpa velit fugiat. Irure cillum qui ullamco cillum ut. Culpa id eu nostrud reprehenderit. Aliquip irure cillum tempor non ex. Ex cillum aute minim ut anim sunt dolore cupidatat exercitation ex. In id nostrud sunt ut ea quis aliqua fugiat nostrud fugiat qui dolore adipisicing.\n\nExercitation sint fugiat ullamco id. Consectetur anim duis dolor eiusmod consectetur enim officia dolor elit velit do in laboris id. Reprehenderit fugiat nostrud ea elit do consectetur anim quis enim esse nostrud. Ea ullamco sit anim consequat anim cillum ullamco nostrud commodo fugiat occaecat mollit sint et. Consequat et do do mollit nostrud eiusmod ut magna. Id tempor cillum duis nisi anim velit officia incididunt. Cillum aliqua pariatur laboris deserunt commodo laboris amet.\n\nBest Regards,\nMays Glass', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'bd7ac4df-77fa-45da-8eaf-31303ba794c4', + type: 'mail', + from: { + avatar: 'images/avatars/male-04.jpg', + contact: 'Barber Zimmerman ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Aug 09 2018 01:33:05 GMT+0000 (UTC)').toISOString(), + subject: + 'Sit elit aliquip sint ullamco tempor in duis Lorem laboris sunt laborum', + content: + 'Hi Brian,\n\nAliquip nisi ullamco cupidatat dolore Lorem consectetur quis eiusmod pariatur. Deserunt quis et veniam ea dolore nisi dolor irure anim nisi reprehenderit ex. Deserunt aliqua eu sunt duis ad veniam exercitation nisi deserunt eu. Sint ad ipsum enim laboris. Ut minim proident ut amet officia sit culpa occaecat dolor consequat aliquip minim elit.\n\nElit dolore minim duis officia ullamco reprehenderit laborum incididunt enim do excepteur voluptate elit. Magna adipisicing cupidatat nisi excepteur. Ipsum ex velit pariatur ea veniam aliquip duis consectetur voluptate. Eu velit exercitation veniam nulla consectetur et reprehenderit ullamco. Laborum nisi occaecat laborum adipisicing. Ullamco culpa qui ex pariatur incididunt anim dolor consectetur fugiat et.\n\nIn sunt consequat consectetur culpa. Id aliquip culpa commodo sunt esse anim nulla quis nisi aute occaecat. Anim enim dolor anim dolore.\n\nKind Regards,\nBarber Zimmerman', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'f5fe9764-70b0-407e-a015-96b04da948a7', + type: 'mail', + from: { + avatar: 'images/avatars/female-04.jpg', + contact: 'Ginger Fry ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Jan 12 2018 15:46:37 GMT+0000 (UTC)').toISOString(), + subject: + 'Fugiat voluptate Lorem id sint enim irure in velit nostrud commodo incididunt', + content: + 'Hey Brian,\n\nCillum sunt irure sit reprehenderit ad do mollit Lorem dolor voluptate magna Lorem ad proident. Aliqua qui incididunt nostrud proident. Occaecat voluptate tempor Lorem magna nostrud. Pariatur excepteur id esse proident enim culpa nostrud consectetur tempor exercitation proident ex voluptate tempor. Do id ullamco qui nostrud est occaecat Lorem ipsum ut consectetur culpa velit sunt. Do nisi laborum dolore dolor eu.\n\nAliquip commodo proident tempor est. Aliqua consequat non irure proident consectetur laborum id cupidatat ex enim culpa adipisicing incididunt. Nisi fugiat nisi id reprehenderit fugiat voluptate nostrud esse deserunt. Laboris commodo aliqua qui pariatur. Quis labore commodo aliquip deserunt.\n\nMinim sint tempor consequat consequat commodo velit magna fugiat dolor consectetur est cillum. Minim consequat do excepteur anim consequat. Anim culpa esse adipisicing culpa sit non ut.\n\nKind Regards,\nGinger Fry', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: '3de07c8c-e687-4138-9967-7fd1feea17ee', + type: 'mail', + from: { + avatar: 'images/avatars/male-05.jpg', + contact: 'Gardner Burnett ', + }, + to: 'me ', + date: new Date('Mon Mar 19 2018 13:34:20 GMT+0000 (UTC)').toISOString(), + subject: 'Labore anim ullamco labore nisi eiusmod duis commodo', + content: + 'Hello Brian,\n\nAute et mollit quis proident proident. Tempor qui sint proident nulla sit ut nulla incididunt enim. Ut cillum nulla nostrud irure sit. Excepteur culpa magna sunt velit consectetur proident labore laborum amet cillum ex elit excepteur nisi. Consectetur voluptate incididunt nulla dolore in culpa excepteur.\n\nQuis duis consequat est elit pariatur deserunt incididunt in enim excepteur deserunt. Aliquip ea exercitation eiusmod deserunt. Qui incididunt consectetur tempor sunt labore id minim deserunt pariatur adipisicing do. Officia nisi pariatur in ea eiusmod Lorem ut commodo.\n\nLaborum ipsum consectetur excepteur cupidatat labore culpa Lorem. Enim pariatur eu aliqua nisi. Tempor pariatur dolore fugiat nulla est tempor incididunt id cupidatat.\n\nCheers!\nGardner Burnett', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: 'd2bbcbbb-aa31-48ee-bbe4-2976b7043e78', + type: 'mail', + from: { + avatar: 'images/avatars/female-05.jpg', + contact: 'Lula Lucas ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Mon Nov 26 2018 16:08:25 GMT+0000 (UTC)').toISOString(), + subject: 'Quis anim labore esse proident', + content: + 'Hi Brian,\n\nEt enim eu esse nostrud minim labore dolor dolor proident ipsum nisi. Occaecat commodo ullamco cupidatat non deserunt eu nisi dolor. Tempor laboris ipsum occaecat consequat reprehenderit do reprehenderit proident elit mollit aliquip officia excepteur eu. Esse excepteur mollit nulla elit non sint. Consectetur sint reprehenderit pariatur pariatur laborum ullamco tempor consectetur consequat proident velit nisi fugiat anim. Lorem reprehenderit enim non excepteur non cupidatat duis aliqua do culpa occaecat velit.\n\nAliqua est ad pariatur ex velit fugiat id do et amet in aliqua. Mollit esse quis culpa mollit. Amet labore nulla qui pariatur aliquip occaecat do ipsum nostrud ipsum consectetur consequat cillum.\n\nNostrud duis cupidatat minim reprehenderit sunt duis consequat veniam enim velit dolore sint. Sint ad aliquip excepteur in tempor anim fugiat ipsum ex ullamco. Aute anim reprehenderit nulla anim pariatur elit mollit et non qui labore culpa laborum pariatur. Cupidatat consequat incididunt aute id. Cillum incididunt ipsum duis reprehenderit cillum ullamco.\n\nKind Regards,\nLula Lucas', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '1a166107-cc66-42dd-96a2-91b1a40f8c62', + type: 'mail', + from: { + avatar: 'images/avatars/male-06.jpg', + contact: 'Mcguire Crosby ', + }, + to: 'me ', + date: new Date('Fri Mar 09 2018 04:54:41 GMT+0000 (UTC)').toISOString(), + subject: + 'Eiusmod esse pariatur ipsum elit laborum tempor cillum amet irure labore duis ad amet aliqua', + content: + 'Hey Brian,\n\nNisi laborum deserunt eiusmod veniam eiusmod aliqua dolor. Nostrud dolor deserunt occaecat ipsum sit. Sint nisi magna adipisicing duis id velit pariatur magna est tempor ad.\n\nEt ea pariatur duis voluptate irure. Dolore nisi exercitation nulla officia. Ea eiusmod amet aliqua ut ea velit veniam eu aliqua. Commodo est nisi nulla nulla in eu dolor eiusmod enim do consequat aliqua reprehenderit. Proident ad nulla reprehenderit incididunt incididunt ut. Anim deserunt officia ad dolor ex occaecat veniam mollit ex voluptate occaecat ullamco amet duis.\n\nDuis esse cillum in sit deserunt. Consequat ut tempor consequat qui. Laboris esse ex est ipsum et id est in magna tempor amet irure veniam. Occaecat veniam pariatur Lorem pariatur mollit ullamco occaecat. Labore dolor sit irure sit. Sunt sint dolore ex voluptate nisi sit cillum fugiat aliqua dolore dolore irure sunt commodo. Anim ullamco duis consequat sint in nulla voluptate velit irure.\n\nKind Regards,\nMcguire Crosby', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: '3fbf66d3-cc2d-4256-a276-ad73da93b7fd', + type: 'mail', + from: { + avatar: 'images/avatars/male-07.jpg', + contact: 'Walsh Bender ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Nov 22 2018 20:26:17 GMT+0000 (UTC)').toISOString(), + subject: + 'Mollit voluptate eu excepteur nisi labore dolor aliquip magna incididunt ipsum quis ex irure', + content: + 'Dear Brian,\n\nOccaecat id commodo aliqua irure officia consectetur exercitation. Dolor ex aliqua velit proident excepteur enim aliqua cupidatat mollit nisi cillum anim reprehenderit. Lorem nulla amet id laborum fugiat mollit ullamco. Cillum in ea ex Lorem cupidatat eiusmod proident. Cillum nulla ullamco excepteur velit eu sint mollit aliqua sint et officia dolor. Est sit laboris non aute aliqua qui non cillum officia cupidatat. Deserunt voluptate ullamco nisi id aute laboris.\n\nNisi sint do consequat mollit fugiat in est quis. Reprehenderit laboris consectetur exercitation anim dolore occaecat sint. Dolore Lorem dolore veniam cillum ea officia.\n\nSint irure sunt sint ullamco. Nisi est dolore ex ea nostrud enim ex deserunt duis enim tempor pariatur. Minim laborum commodo officia officia do deserunt. Officia consequat elit deserunt quis tempor eiusmod irure sint Lorem magna ea culpa. Adipisicing labore sint elit ex commodo esse duis eiusmod. Id quis non fugiat amet incididunt cillum tempor voluptate.\n\nKind Regards,\nWalsh Bender', + attachments: [ + { + type: 'image/jpeg', + name: 'mystery-forest.jpg', + size: 15539, + preview: 'mystery-forest_preview.jpg', + downloadUrl: '', + }, + { + type: 'application/pdf', + name: 'montly-invoice.pdf', + size: 243449, + preview: 'pdf', + downloadUrl: '', + }, + ], + starred: false, + important: false, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'b460fff2-8530-4464-8c44-744cd3de3bf8', + type: 'mail', + from: { + avatar: 'images/avatars/male-08.jpg', + contact: 'Baker Guthrie ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sat Jul 21 2018 07:51:09 GMT+0000 (UTC)').toISOString(), + subject: 'In ipsum elit esse laboris qui', + content: + 'Hey Brian,\n\nId magna ut laborum cillum nisi mollit reprehenderit consectetur aliquip laborum. Tempor excepteur enim esse officia ex Lorem quis exercitation irure ut nisi ex ipsum. Dolor consectetur sint incididunt reprehenderit reprehenderit magna in. Ullamco labore aute dolor do ad mollit velit aliqua. Amet consectetur adipisicing dolore tempor ea. Cupidatat magna occaecat aliquip non eiusmod dolore aliquip cillum irure.\n\nDolore laborum sunt fugiat officia voluptate consectetur sint enim qui dolor cupidatat consequat cupidatat elit. Commodo Lorem ut ut nostrud duis. Id nisi adipisicing incididunt dolor voluptate sit ad cupidatat voluptate. Labore consequat exercitation sint occaecat eu cupidatat incididunt irure ullamco et aute anim cupidatat. Quis aliqua ut eu sunt id.\n\nDeserunt veniam dolor exercitation labore do enim nisi. Veniam ipsum duis consectetur ex voluptate incididunt dolore laborum ad consequat. Fugiat Lorem pariatur duis nostrud duis aliqua ex do. Nisi sunt eiusmod minim exercitation exercitation aliquip non labore nulla proident nisi ipsum. Sunt elit esse officia cupidatat cupidatat. Ad fugiat est ex fugiat.\n\nKind Regards,\nBaker Guthrie', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '16c18231-82ea-403d-895a-2a4bc27b61ca', + type: 'mail', + from: { + avatar: 'images/avatars/female-06.jpg', + contact: 'Olivia Ratliff ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Mon Feb 19 2018 13:18:23 GMT+0000 (UTC)').toISOString(), + subject: 'Mollit ex magna non Lorem id', + content: + 'Hi Brian,\n\nNostrud dolore tempor amet nostrud ex aliqua duis. Aliqua enim reprehenderit magna et occaecat. Officia veniam sint fugiat dolor esse ullamco ad non nulla deserunt ullamco commodo occaecat consequat.\n\nSunt nisi reprehenderit nisi incididunt sunt do veniam sint proident duis labore nostrud. Nostrud ad voluptate nisi dolor labore Lorem ex minim nostrud ipsum do. Eiusmod officia sunt tempor duis sunt. In anim in excepteur velit id commodo non nisi aute nisi labore. Ea esse velit eiusmod nulla nisi id eiusmod ex mollit voluptate ad ut ea. Reprehenderit magna quis reprehenderit velit ea veniam magna sint ipsum nulla est officia.\n\nEst ex nostrud quis amet mollit aliquip. Et mollit amet id anim sint amet. Officia do nostrud laboris ullamco cupidatat labore quis exercitation proident aliqua. Eiusmod dolore consectetur nisi deserunt culpa occaecat eu culpa do. Voluptate officia dolore non deserunt. Dolore culpa fugiat eiusmod aliquip. Eu laborum irure fugiat duis esse mollit laborum sit et excepteur irure ipsum.\n\nKind Regards,\nOlivia Ratliff', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'dfa4d802-b833-49c1-afdc-02116d73e35a', + type: 'mail', + from: { + avatar: 'images/avatars/male-09.jpg', + contact: 'Schneider Kirby ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Mon Nov 12 2018 09:55:21 GMT+0000 (UTC)').toISOString(), + subject: + 'Occaecat eiusmod cillum nostrud dolore et proident est esse magna mollit enim', + content: + 'Hello Brian,\n\nNon in est voluptate veniam do minim. Ullamco mollit occaecat officia irure tempor deserunt tempor magna voluptate. Id commodo voluptate commodo qui aliqua excepteur aute in eiusmod occaecat quis velit veniam id. Ipsum in sunt aliqua ad eu. Consequat enim commodo ex excepteur pariatur ut.\n\nSunt officia nisi deserunt culpa mollit et duis duis id in nisi. Eiusmod mollit ea qui laborum veniam adipisicing ullamco adipisicing dolor quis enim laboris dolor. Culpa exercitation velit mollit labore incididunt. Veniam deserunt ex ea quis ullamco. Et ex laborum officia non et.\n\nAmet exercitation irure mollit nostrud. Officia dolore nostrud ad do ipsum et laborum consequat ullamco sint consequat amet. Nisi adipisicing ullamco aliqua Lorem quis sint magna veniam. Consequat mollit dolore aliqua ad occaecat. Voluptate aute ea quis sit enim aliquip. Eu dolore nulla minim eu esse minim non cupidatat voluptate laborum do non et. Ut pariatur cillum non labore nostrud amet consectetur consectetur eu cillum.\n\nKind Regards,\nSchneider Kirby', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '84c4ef35-da0d-4d1f-a966-f7f413545b04', + type: 'mail', + from: { + avatar: 'images/avatars/male-10.jpg', + contact: 'Griffith Keith ', + }, + to: 'me ', + date: new Date('Tue Dec 11 2018 06:27:01 GMT+0000 (UTC)').toISOString(), + subject: + 'Ex occaecat qui veniam qui consectetur aliquip ad reprehenderit laborum proident', + content: + 'Hi Brian,\n\nExercitation labore cupidatat incididunt velit laboris ipsum anim commodo in do fugiat ea. Incididunt labore quis pariatur laboris sint tempor. Lorem commodo do do ipsum aliqua. Consectetur occaecat ad incididunt consectetur do excepteur ea laborum. Laboris enim proident excepteur ea exercitation deserunt. Sit dolor fugiat velit adipisicing proident ut cillum nisi adipisicing.\n\nConsectetur quis id non mollit minim. Consectetur ut cupidatat enim occaecat sint ex dolor sunt pariatur mollit exercitation. Magna Lorem aliqua nostrud aute ut enim laboris dolore eiusmod est occaecat dolor fugiat occaecat.\n\nCommodo exercitation sit laboris aute deserunt nostrud occaecat do dolore cupidatat consectetur commodo. Cupidatat laborum excepteur voluptate commodo irure. Excepteur enim labore dolore adipisicing ut aute irure. Labore sunt dolor sint magna dolor consequat. Nulla deserunt mollit cillum adipisicing enim est voluptate minim pariatur aliqua elit sint do eiusmod.\n\nCheers!\nGriffith Keith', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '0c78627f-5cbe-4d21-8491-455e98bf6f69', + type: 'mail', + from: { + avatar: 'images/avatars/female-07.jpg', + contact: 'Beverly Pugh ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sun Oct 21 2018 14:36:41 GMT+0000 (UTC)').toISOString(), + subject: + 'Mollit irure adipisicing in consectetur aliqua labore pariatur minim', + content: + 'Hello Brian,\n\nAute in dolore irure non exercitation. Laborum enim qui nulla irure enim id labore excepteur eiusmod consectetur consequat voluptate exercitation. In laborum reprehenderit incididunt occaecat laborum sit velit.\n\nNulla aliquip labore mollit qui dolore consequat. Enim sunt est nisi reprehenderit tempor amet culpa ex. Eiusmod esse ullamco veniam sunt anim nisi dolore cupidatat id aute.\n\nLorem commodo Lorem qui aliquip eiusmod nisi cupidatat occaecat. Aliqua eiusmod nisi laboris elit commodo qui. Voluptate veniam aliquip ad et pariatur voluptate. Officia non ea laboris dolor excepteur ullamco Lorem dolor esse aute excepteur cillum magna. Cillum et anim adipisicing occaecat consectetur. Non aute culpa pariatur aute fugiat in sint exercitation cillum laborum est non.\n\nBest Regards,\nBeverly Pugh', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '3d382fb3-b5f5-43e3-b0bc-f2a6f29a5ee6', + type: 'mail', + from: { + avatar: 'images/avatars/female-08.jpg', + contact: 'Ila Mclaughlin ', + }, + to: 'me ', + date: new Date('Wed Aug 01 2018 13:55:15 GMT+0000 (UTC)').toISOString(), + subject: 'Enim proident sit dolor officia sit magna ea tempor', + content: + 'Hey Brian,\n\nExcepteur ut minim qui minim mollit. Tempor cupidatat Lorem sint aliquip excepteur sunt est velit nostrud ea. Velit esse ea irure veniam. Dolore tempor nisi occaecat tempor laborum et nulla enim do sint.\n\nId officia ea ad ad occaecat occaecat consequat veniam ad magna cillum incididunt quis tempor. Veniam reprehenderit qui excepteur sint sunt proident ipsum. Exercitation nostrud eiusmod incididunt consequat enim velit sit qui veniam consectetur. Est exercitation tempor ea sunt enim nulla ea proident officia pariatur. Dolor aute exercitation cillum dolore eu sunt veniam id dolore voluptate ut nostrud deserunt. Eiusmod labore anim veniam labore anim nostrud ad nulla labore consequat enim nisi.\n\nLaboris fugiat quis aute duis reprehenderit ut pariatur non incididunt excepteur ea ut. Nisi deserunt tempor Lorem commodo ad pariatur aliquip duis tempor officia irure. Eu fugiat dolore exercitation veniam cillum.\n\nBest Regards,\nIla Mclaughlin', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '7fd27d57-ccca-432d-af26-c3b609448fb7', + type: 'mail', + from: { + avatar: 'images/avatars/female-09.jpg', + contact: 'Jenna Manning ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sat Dec 08 2018 20:09:08 GMT+0000 (UTC)').toISOString(), + subject: + 'Quis deserunt excepteur eiusmod reprehenderit enim exercitation voluptate anim', + content: + 'Hi Brian,\n\nExcepteur qui anim sint elit. Ad et nostrud non et reprehenderit duis pariatur irure deserunt commodo sit reprehenderit tempor reprehenderit. Aliquip duis mollit duis consequat aute non id do irure. Aute esse sunt labore et voluptate sunt adipisicing tempor. Occaecat tempor exercitation sit duis fugiat ea irure laborum. Consequat quis officia magna fugiat ex.\n\nAliqua non in elit ipsum enim duis dolore laboris in esse duis sunt ea anim. Et laborum sunt in anim ut consequat laborum irure. Deserunt ex veniam laborum tempor cupidatat amet mollit non labore.\n\nDuis veniam occaecat eiusmod velit Lorem ad. Commodo cillum minim id pariatur cupidatat tempor reprehenderit commodo. Dolor anim pariatur nulla qui ut magna nisi reprehenderit sit cupidatat est do. Esse enim Lorem laboris amet reprehenderit pariatur et nostrud minim pariatur. Et dolore qui quis ex consequat consectetur enim veniam veniam veniam magna.\n\nKind Regards,\nJenna Manning', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '18ba20d3-d7bc-4fc7-85dd-2db4db3196fe', + type: 'mail', + from: { + avatar: 'images/avatars/female-10.jpg', + contact: 'Deann Hansen ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Jan 10 2019 12:13:47 GMT+0000 (UTC)').toISOString(), + subject: + 'Duis minim quis in labore voluptate laboris do consequat eu anim sit deserunt incididunt', + content: + 'Dear Brian,\n\nPariatur ipsum ipsum aute dolore ipsum ea mollit labore duis tempor aliquip et reprehenderit. Pariatur amet esse minim ad esse aute excepteur in. Quis eu laborum dolore ullamco ipsum incididunt fugiat non laborum est. Duis anim incididunt Lorem Lorem nulla fugiat qui reprehenderit pariatur. Ut est duis in quis excepteur officia. Ullamco excepteur dolor cillum non aliqua non aliqua sit cillum ipsum laboris proident sint. Nulla in dolore deserunt proident commodo enim occaecat cupidatat reprehenderit incididunt dolor laborum do aliqua.\n\nLaboris velit tempor non adipisicing pariatur culpa culpa amet sint deserunt enim. Est aute sit officia quis ex do id ex deserunt ea. Velit in dolor quis exercitation proident mollit sit ad veniam nisi. Cupidatat esse exercitation commodo velit. Commodo veniam occaecat elit deserunt. Sint adipisicing culpa aute occaecat nisi id consequat nisi.\n\nAdipisicing veniam deserunt ipsum mollit. Adipisicing laborum exercitation sint nulla veniam ex ut dolor. Fugiat do ad proident tempor. Id ipsum ex elit id quis laboris ut irure nulla minim reprehenderit minim dolor sunt. Anim nisi cupidatat sint minim fugiat sit sit cupidatat laborum excepteur duis exercitation anim commodo.\n\nBest Regards,\nDeann Hansen', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '476cb471-b3c1-4235-b5ef-3066b028483d', + type: 'mail', + from: { + avatar: 'images/avatars/female-11.jpg', + contact: 'Tisha Moore ', + }, + to: 'me ', + date: new Date('Wed Mar 07 2018 05:58:09 GMT+0000 (UTC)').toISOString(), + subject: 'Qui irure ea qui labore fugiat ad voluptate esse', + content: + 'Hey Brian,\n\nAnim ex voluptate in amet duis labore. Esse id ut exercitation labore velit irure amet laborum. Aliqua ex et est reprehenderit amet quis anim ut qui dolore et sit ea amet.\n\nEnim incididunt sunt deserunt voluptate tempor ut minim laborum pariatur Lorem esse ex cillum. Irure proident amet labore aliqua elit excepteur. Pariatur fugiat sit duis ut in elit.\n\nLorem proident duis occaecat eu proident reprehenderit incididunt amet magna officia et ut. Nisi do labore sit sit eiusmod nostrud consectetur proident enim. Ipsum culpa proident consequat nostrud.\n\nCheers!\nTisha Moore', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'a8aea501-17e9-4e40-b3a9-04f33b13cb8a', + type: 'mail', + from: { + avatar: 'images/avatars/female-12.jpg', + contact: 'Janette Elliott ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Mon May 07 2018 20:07:15 GMT+0000 (UTC)').toISOString(), + subject: + 'Veniam amet voluptate Lorem qui ut id culpa mollit reprehenderit est', + content: + 'Hi Brian,\n\nAute veniam laborum ad veniam adipisicing pariatur pariatur eiusmod nulla. Labore ullamco cillum cillum eiusmod id ex occaecat cupidatat ea consequat consequat Lorem amet consectetur. Ullamco irure enim officia ut nostrud. Nisi enim aliqua excepteur voluptate et amet commodo aliqua nulla. Adipisicing in eiusmod in commodo veniam id ea ad dolor sunt pariatur ea non consequat.\n\nDeserunt mollit dolor quis irure ullamco. Consectetur Lorem ipsum ex proident aute aute occaecat adipisicing mollit. Lorem mollit consectetur ad dolor enim mollit non. Laboris pariatur laborum minim magna culpa fugiat ad. Duis ea Lorem cillum adipisicing Lorem.\n\nEnim laboris laboris magna culpa. Consectetur anim occaecat commodo labore nostrud id pariatur. Dolore aliquip irure laborum pariatur mollit sit aute minim in nisi.\n\nCheers!\nJanette Elliott', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '1ee2ea29-9a1f-4c27-b4d2-5e465703b6a0', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'cb55f824-40ed-4696-afaa-f3f8d3fb8614', + type: 'mail', + from: { + avatar: 'images/avatars/female-13.jpg', + contact: 'Bettie Wyatt ', + }, + to: 'me ', + date: new Date('Mon Dec 03 2018 17:07:32 GMT+0000 (UTC)').toISOString(), + subject: + 'Est aliquip nisi pariatur cupidatat veniam qui cillum eu sit ullamco voluptate minim', + content: + 'Dear Brian,\n\nEsse laboris commodo ullamco dolor ipsum. Aute cillum velit in aliquip ad adipisicing pariatur ex tempor cillum eu cupidatat laborum. Consectetur aute cupidatat incididunt quis minim elit cupidatat.\n\nEiusmod ea eiusmod nostrud qui amet pariatur laboris non. Proident dolor et pariatur id duis minim enim. Aute ut dolore cupidatat velit sunt.\n\nVeniam magna laborum tempor nostrud aliqua tempor. Pariatur in do id do exercitation non dolor. In elit velit ad Lorem veniam minim Lorem voluptate sint ullamco consectetur aute est. Enim sunt reprehenderit id nisi id. Eu sint incididunt nulla consequat veniam reprehenderit.\n\nBest Regards,\nBettie Wyatt', + attachments: [], + starred: true, + important: false, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: '92dddecc-4758-4c48-8ef4-a59de4ab0705', + type: 'mail', + from: { + avatar: 'images/avatars/male-11.jpg', + contact: 'Hardy Dale ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Nov 16 2018 21:53:34 GMT+0000 (UTC)').toISOString(), + subject: 'Aute commodo nostrud nisi quis sunt ex', + content: + 'Dear Brian,\n\nDeserunt laborum elit in dolor. Enim duis occaecat minim dolor ex. Consequat et anim mollit nisi ex exercitation culpa non esse velit veniam ad. Sit excepteur nulla laboris reprehenderit. Consequat labore cupidatat Lorem proident proident laborum adipisicing aliqua commodo voluptate esse officia.\n\nAliqua adipisicing sint Lorem id in ad qui. Reprehenderit ullamco labore consectetur commodo Lorem eiusmod culpa. Lorem voluptate ipsum anim ipsum do ullamco eiusmod ad est proident officia. Ut anim deserunt minim laborum minim ea et minim non ad ut. Non ipsum et enim pariatur.\n\nNulla voluptate consectetur id aute ad officia incididunt velit voluptate aliqua deserunt ex. Dolore commodo labore aliqua aliquip magna ipsum laboris cupidatat velit. Et elit labore eu id laborum. Ipsum est pariatur irure aute magna. Fugiat sit voluptate eiusmod consequat.\n\nKind Regards,\nHardy Dale', + attachments: [], + starred: false, + important: true, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '3f3f6154-a8ff-40d6-8b57-4c0f73d2cd8b', + type: 'mail', + from: { + avatar: 'images/avatars/female-14.jpg', + contact: 'Eileen Bush ', + }, + to: 'me ', + date: new Date('Sat Feb 24 2018 12:22:13 GMT+0000 (UTC)').toISOString(), + subject: 'Lorem deserunt dolor sunt sit sit mollit', + content: + 'Hey Brian,\n\nEu Lorem laborum qui elit id consequat ipsum ex. Minim aute proident aliquip non esse quis voluptate nisi enim. Velit Lorem ea exercitation adipisicing amet deserunt pariatur Lorem amet anim dolore ipsum. Tempor magna voluptate laboris fugiat. Elit ea fugiat cillum Lorem fugiat ipsum officia tempor sit excepteur pariatur minim sint proident.\n\nDeserunt adipisicing dolor do fugiat commodo dolor sit ut culpa ea officia Lorem officia. Nulla elit tempor nostrud nulla. Amet tempor deserunt labore irure est ut officia ullamco velit. Officia tempor anim ex dolor consequat dolore anim do velit qui laboris nisi ipsum. Aute ipsum aliqua ut ullamco laborum pariatur minim mollit consectetur ipsum.\n\nNisi commodo labore nostrud veniam ut aute dolore veniam in. Consectetur commodo proident incididunt aliqua reprehenderit ex nostrud est magna elit reprehenderit. Proident veniam sint occaecat ullamco labore aliquip eiusmod duis sint. Ea ex id eiusmod eu elit ullamco aliqua. Dolore consectetur magna eu voluptate ea aliquip eu et veniam ullamco deserunt magna.\n\nKind Regards,\nEileen Bush', + attachments: [], + starred: true, + important: false, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '2c80bcbe-c9cd-4eec-83fa-b9994713a784', + type: 'mail', + from: { + avatar: 'images/avatars/female-15.jpg', + contact: 'Melody Mcintosh ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Fri Jul 27 2018 07:20:48 GMT+0000 (UTC)').toISOString(), + subject: + 'Reprehenderit consequat aliquip duis incididunt excepteur aliquip excepteur velit labore laboris', + content: + 'Hey Brian,\n\nEnim ea ad veniam qui magna aliqua ipsum id anim adipisicing voluptate id velit. Elit pariatur magna quis ea adipisicing deserunt officia consectetur in magna culpa sint. Culpa cupidatat anim amet ea elit adipisicing sunt. Incididunt nulla est non cupidatat sit excepteur consectetur culpa labore in sit. Aliqua cupidatat aute qui esse labore aliquip sit exercitation aliqua est magna sint nisi quis.\n\nTempor laboris ullamco culpa dolor ipsum ad aliqua consequat anim reprehenderit aliqua. Proident elit mollit commodo ut Lorem incididunt cillum Lorem eu adipisicing fugiat. Sint velit eiusmod magna occaecat tempor nulla ex ea.\n\nDolore est proident ea deserunt dolore non elit Lorem ipsum sint. Quis ut pariatur cupidatat deserunt. Aliqua sunt labore dolore officia ullamco exercitation id excepteur est et eu consequat esse consectetur. Veniam eu culpa reprehenderit id fugiat aliqua anim id esse commodo velit labore adipisicing. In anim Lorem reprehenderit occaecat do laboris veniam cillum incididunt aute dolor id duis. Est pariatur Lorem consectetur proident est culpa ullamco ea elit incididunt veniam enim elit ipsum.\n\nCheers!\nMelody Mcintosh', + attachments: [], + starred: false, + important: true, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '2a44e5b7-e01c-43db-b586-540390f46f95', + type: 'mail', + from: { + avatar: 'images/avatars/male-12.jpg', + contact: 'Dale Talley ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Tue Jan 02 2018 07:20:01 GMT+0000 (UTC)').toISOString(), + subject: + 'Aute occaecat est amet adipisicing aute voluptate incididunt adipisicing pariatur esse anim ipsum labore', + content: + 'Hello Brian,\n\nNostrud deserunt do anim pariatur adipisicing cillum enim. Reprehenderit ex ut ullamco deserunt non voluptate laboris eu deserunt sint incididunt anim. Minim non amet quis officia ullamco non do id. Aute deserunt enim laborum elit magna veniam do. Id Lorem mollit eu id ex eiusmod fugiat ad sint quis sunt ipsum eu exercitation. Eiusmod occaecat sit esse cupidatat occaecat quis commodo magna qui. Non consectetur non ad cillum minim eiusmod magna ad nulla.\n\nAd esse anim nostrud do. Officia eiusmod eu reprehenderit do ipsum velit enim. In ullamco ex commodo laboris ut adipisicing elit sit occaecat fugiat officia. Commodo duis aliqua minim nisi.\n\nDuis proident mollit ea do Lorem excepteur. Commodo ex duis minim aute Lorem. Est in quis aliquip in qui minim excepteur aliqua dolor adipisicing. Laborum do tempor voluptate anim elit nisi minim sunt cupidatat.\n\nKind Regards,\nDale Talley', + attachments: [], + starred: true, + important: true, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'be1e969d-d52f-4824-83e5-c184b042b601', + type: 'mail', + from: { + avatar: 'images/avatars/male-13.jpg', + contact: 'Britt Massey ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Oct 09 2018 00:12:17 GMT+0000 (UTC)').toISOString(), + subject: 'Cillum tempor cillum ex esse eu Lorem', + content: + 'Hey Brian,\n\nVelit laboris excepteur nulla aliquip excepteur. Culpa incididunt magna minim non aute incididunt officia cillum quis labore pariatur tempor amet. Eiusmod nulla consectetur mollit anim anim aute quis reprehenderit sint eu. Voluptate deserunt voluptate est adipisicing adipisicing qui ullamco ex officia eiusmod. Velit nostrud Lorem enim aute sint anim cupidatat excepteur ea aliqua et. Minim et do irure ut commodo. Cillum quis nulla commodo ipsum tempor.\n\nCulpa duis laboris anim nostrud ipsum laboris exercitation exercitation ut. Dolore duis laborum do sunt non. Eiusmod irure consectetur sint do. Officia adipisicing est id reprehenderit labore.\n\nConsequat eiusmod est labore commodo Lorem ullamco cupidatat id Lorem eiusmod amet do enim qui. Consequat ad ipsum elit non esse labore. Cupidatat eu reprehenderit ad est cillum esse. Nulla eiusmod eiusmod mollit culpa quis sint sit elit aliquip. Incididunt quis consequat amet ipsum amet. Ut officia eu consectetur incididunt adipisicing tempor aute. Reprehenderit labore consequat excepteur esse adipisicing.\n\nBest Regards,\nBritt Massey', + attachments: [], + starred: true, + important: false, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: '8c7c9bff-6ca6-47ba-9242-cd9b1715eac7', + type: 'mail', + from: { + avatar: 'images/avatars/female-16.jpg', + contact: 'Madelyn Haynes ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sat Mar 03 2018 18:08:05 GMT+0000 (UTC)').toISOString(), + subject: 'Irure enim minim dolore et', + content: + 'Dear Brian,\n\nSit incididunt Lorem ad cupidatat eiusmod dolore velit. Aliquip dolore quis qui irure consequat velit. Dolor incididunt est magna sit.\n\nConsequat officia nulla culpa officia eiusmod sint. Proident elit irure pariatur eiusmod enim mollit mollit qui. Amet ullamco aliquip sunt fugiat proident est nulla ex ex est ipsum officia. Exercitation qui veniam duis ut veniam aliqua excepteur.\n\nDeserunt ipsum eiusmod labore do minim enim anim. Aliquip mollit magna do eu. Qui duis consectetur exercitation veniam qui. Ea duis nisi aute aute occaecat deserunt magna.\n\nBest Regards,\nMadelyn Haynes', + attachments: [], + starred: false, + important: false, + unread: false, + folder: 'fbdc8e79-a0c4-4a27-bc98-9c81ee7a86e5', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'afd52898-82e9-4fd4-8d6a-2391ec817e2a', + type: 'mail', + from: { + avatar: 'images/avatars/male-14.jpg', + contact: 'Rowland Sweet ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Jun 22 2018 14:39:18 GMT+0000 (UTC)').toISOString(), + subject: + 'Sunt et proident ullamco qui id irure occaecat anim aute elit amet', + content: + 'Hey Brian,\n\nLaborum sunt labore esse occaecat duis magna aliquip dolor. Cupidatat irure aute enim minim nisi reprehenderit voluptate occaecat exercitation anim est quis. Magna esse ipsum tempor consectetur non excepteur nostrud. Est consectetur tempor deserunt magna anim culpa sint id elit. Et ullamco non non aute id cupidatat proident aute dolor exercitation excepteur.\n\nEx officia quis irure enim eu in qui sit non. Aliqua fugiat ut sint deserunt veniam quis qui cillum dolor commodo ea exercitation. Deserunt id velit deserunt Lorem elit elit non. Voluptate fugiat est adipisicing non eiusmod voluptate. Reprehenderit tempor id quis cillum nulla sint sit ipsum veniam aliqua.\n\nPariatur dolore ipsum proident irure elit dolore pariatur occaecat qui adipisicing occaecat et aliquip. Ex quis mollit ad Lorem laboris dolor incididunt. Ex consectetur nulla nulla labore dolor. Cillum aliqua duis nulla quis sit laborum magna et exercitation anim deserunt incididunt elit.\n\nBest Regards,\nRowland Sweet', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: 'e43d83f0-51ea-4da9-b18f-0de380b5156a', + type: 'mail', + from: { + avatar: 'images/avatars/female-17.jpg', + contact: 'Fern Shaw ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Wed Feb 21 2018 06:23:12 GMT+0000 (UTC)').toISOString(), + subject: + 'Nisi esse incididunt nisi aliqua aliqua laborum occaecat quis duis minim', + content: + 'Dear Brian,\n\nCommodo magna officia qui labore ullamco nostrud. Labore nostrud consequat quis laborum esse velit dolore eu. Esse officia duis nisi dolore culpa elit velit excepteur irure in excepteur officia. Ad anim magna excepteur deserunt aute dolore nulla dolore aute tempor laboris adipisicing. Sit culpa culpa occaecat nostrud magna labore sunt pariatur exercitation qui. Esse ea mollit non sint cupidatat aliqua mollit qui ex tempor exercitation labore minim. Veniam aliquip exercitation ad ex.\n\nOfficia nulla exercitation ullamco proident eu amet occaecat Lorem est est non nulla tempor. Exercitation occaecat ipsum sunt tempor et enim nisi reprehenderit Lorem. Culpa elit ea aliqua est. Et exercitation aliquip aliqua et magna et magna exercitation aliqua exercitation pariatur occaecat.\n\nUllamco ea esse consequat nisi et officia veniam. Duis esse ullamco cupidatat do eiusmod aliquip velit irure sit ad irure officia. Irure veniam sunt aliqua elit ex Lorem qui ullamco qui enim dolore aliquip consectetur quis.\n\nBest Regards,\nFern Shaw', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: '4e1d4820-e907-405a-990a-4a5772f482ad', + type: 'mail', + from: { + avatar: 'images/avatars/female-18.jpg', + contact: 'Lorraine Ballard ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sun Jun 24 2018 13:46:02 GMT+0000 (UTC)').toISOString(), + subject: 'Velit nisi ut laboris id et do aliquip', + content: + 'Hey Brian,\n\nOccaecat fugiat minim dolor excepteur. Ullamco quis cillum consectetur mollit id. Duis eiusmod occaecat consectetur duis laborum aliqua occaecat cillum laboris proident cillum. Amet in elit dolore quis amet cupidatat id laboris Lorem do velit occaecat nostrud ad. Eu est pariatur nulla est proident Lorem quis velit fugiat nisi quis. Dolor mollit occaecat duis veniam amet Lorem tempor eiusmod. Consequat ullamco deserunt nulla occaecat officia est qui pariatur velit.\n\nConsequat culpa do ullamco est et veniam incididunt. Exercitation laborum eu proident ex nostrud minim eu proident ad Lorem aute est non ea. Ut quis mollit anim sunt et labore deserunt tempor magna exercitation irure dolor incididunt elit. Mollit amet ad Lorem eiusmod cupidatat adipisicing nostrud. Non dolor ad occaecat officia in dolor. Irure sint quis aliqua deserunt dolore qui sunt cupidatat quis aliqua eu Lorem. Elit sint incididunt do ea amet laborum.\n\nReprehenderit reprehenderit veniam sit exercitation. Tempor ea adipisicing consequat nostrud sint irure nostrud. Dolor est officia adipisicing dolor et pariatur proident quis sint pariatur exercitation commodo. Esse id quis qui aliquip quis.\n\nKind Regards,\nLorraine Ballard', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'f0f2d069-50a0-484f-a4a2-3a272fcb433b', + type: 'mail', + from: { + avatar: 'images/avatars/male-15.jpg', + contact: 'Jennings Franklin ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Thu Apr 26 2018 06:34:54 GMT+0000 (UTC)').toISOString(), + subject: + 'Enim esse aliqua elit incididunt aliqua mollit amet quis occaecat veniam occaecat proident', + content: + 'Hey Brian,\n\nConsectetur laboris voluptate cillum tempor aliqua ullamco sunt do est ea non mollit cupidatat cillum. Aute aliqua eiusmod proident officia enim duis est elit. Fugiat occaecat culpa aliqua in velit elit duis reprehenderit enim dolor eiusmod sit incididunt. Commodo velit ipsum elit amet qui laboris culpa aliquip ad dolor sit. Commodo laboris laborum eiusmod qui in esse excepteur laboris in ad proident deserunt adipisicing. Esse aliqua sit ad veniam sint et culpa minim labore.\n\nUt officia sint nulla enim consectetur eiusmod. Minim nulla cillum aliquip elit ad pariatur ea nulla. Anim occaecat nulla irure ea nisi dolor cupidatat et consectetur id tempor aliqua magna esse. Laborum culpa incididunt cillum excepteur. Nulla laborum dolore qui sit laboris et mollit labore ea magna occaecat. Dolore cillum fugiat eiusmod Lorem ea veniam.\n\nEt cillum enim nulla anim ipsum labore quis amet exercitation occaecat. Et sint eiusmod sint minim do. In esse id ea ut excepteur amet minim Lorem id consectetur cupidatat est. Dolor pariatur commodo eu est. Consequat aliquip est qui mollit excepteur ex do adipisicing.\n\nKind Regards,\nJennings Franklin', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: '7c93dbde-8b56-43a3-ade1-b132906679c4', + type: 'mail', + from: { + avatar: 'images/avatars/female-19.jpg', + contact: 'Colette Cole ', + }, + to: 'me ', + date: new Date('Fri Sep 21 2018 08:37:21 GMT+0000 (UTC)').toISOString(), + subject: 'Sit non consequat et fugiat id laboris', + content: + 'Hello Brian,\n\nIpsum non nisi ut do occaecat pariatur et duis eiusmod sint. Consequat do eu adipisicing fugiat incididunt dolor proident cupidatat reprehenderit dolor proident. Duis non eiusmod sit enim esse fugiat sit amet tempor ea ad adipisicing quis aliquip. Labore in cillum ipsum officia est irure incididunt fugiat id in eu Lorem. Occaecat eu veniam voluptate fugiat fugiat laborum veniam voluptate. Do exercitation mollit fugiat duis deserunt. Mollit aute non quis ex officia non amet amet dolor ullamco sint sunt.\n\nDolore incididunt ad ad anim minim. Sunt reprehenderit duis occaecat labore dolore irure elit. Excepteur laboris et proident nulla magna duis et. Eiusmod veniam ea culpa cillum incididunt ad officia. Labore in non voluptate ullamco velit ex aliqua aliqua amet nisi incididunt exercitation. Labore qui est adipisicing labore anim officia ullamco aliquip anim commodo ipsum mollit.\n\nQuis cillum non consequat ea esse laboris duis mollit et id consectetur nisi eu pariatur. In fugiat culpa aliqua eu culpa ea nisi incididunt. Pariatur excepteur laborum nulla ea ex reprehenderit ea deserunt.\n\nKind Regards,\nColette Cole', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '7335d11d-04b1-4250-817a-6fdf3e099239', + type: 'mail', + from: { + avatar: 'images/avatars/female-20.jpg', + contact: 'Kim Carlson ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Wed Nov 28 2018 07:57:15 GMT+0000 (UTC)').toISOString(), + subject: 'Ullamco ad consectetur ut esse', + content: + 'Dear Brian,\n\nReprehenderit aliqua nulla tempor nostrud. Excepteur nostrud nostrud velit magna pariatur nisi anim excepteur consectetur proident irure cillum dolore et. Nostrud esse deserunt veniam exercitation et dolore fugiat tempor. Sit duis adipisicing sint aliqua. Ipsum anim sit velit ipsum ipsum occaecat sit ea Lorem ex pariatur. Anim laboris exercitation tempor officia in enim.\n\nEsse sunt aliquip nulla ipsum laborum irure minim Lorem eiusmod ut et occaecat. Ipsum deserunt nulla duis nisi dolore. Aliquip ea exercitation magna esse laboris consectetur culpa labore reprehenderit ipsum dolor veniam sunt. In consequat cillum irure elit sunt ea Lorem mollit sint tempor reprehenderit laborum velit consequat. Proident aute deserunt Lorem tempor labore incididunt eu adipisicing proident nulla anim exercitation. Laborum adipisicing id id aliquip. Velit proident consectetur consequat nisi dolor aliquip mollit ad officia proident ea nostrud ea.\n\nProident sunt velit adipisicing voluptate amet est dolor reprehenderit deserunt laboris ullamco velit. Veniam nulla esse cupidatat magna ut consequat in et ipsum mollit ad non Lorem. Sit duis qui eiusmod exercitation commodo voluptate laborum. Exercitation consequat mollit nisi mollit deserunt sunt. Fugiat incididunt magna ad nostrud non amet incididunt veniam et veniam nulla ipsum fugiat.\n\nBest Regards,\nKim Carlson', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'e9dd17ec-4fc3-424e-89e5-c58d9d235764', + type: 'mail', + from: { + avatar: 'images/avatars/female-01.jpg', + contact: 'Rena Park ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sat Oct 13 2018 12:57:58 GMT+0000 (UTC)').toISOString(), + subject: 'Nulla qui anim ullamco officia elit', + content: + 'Hi Brian,\n\nDuis qui qui ad aliqua ad incididunt non exercitation do qui aliqua quis. Eiusmod amet irure minim fugiat ex et sint. Occaecat laboris minim eu exercitation nostrud commodo qui deserunt. Sunt voluptate eiusmod minim commodo. Est excepteur est irure laborum mollit qui excepteur culpa consequat.\n\nEx consectetur minim elit excepteur ad ad ea anim in elit. Velit dolor qui incididunt pariatur aliquip reprehenderit laboris fugiat. Veniam laboris elit ipsum eiusmod dolore labore excepteur enim pariatur.\n\nDolore sit aliquip velit voluptate ipsum in dolor laboris nisi occaecat minim. Ad sunt reprehenderit sit proident occaecat amet aute esse enim. Elit aute adipisicing mollit reprehenderit tempor ex elit proident. Aute exercitation commodo esse exercitation.\n\nBest Regards,\nRena Park', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: '5fb8f4b5-fad4-4a82-8519-a732e68a48be', + type: 'mail', + from: { + avatar: 'images/avatars/male-16.jpg', + contact: 'Haney Gibson ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Tue Oct 23 2018 00:16:47 GMT+0000 (UTC)').toISOString(), + subject: 'Veniam consequat ipsum occaecat ipsum', + content: + 'Hey Brian,\n\nMinim adipisicing sint do irure. Reprehenderit reprehenderit dolor tempor anim exercitation qui aliqua. Laboris consectetur labore quis deserunt culpa amet eiusmod minim cillum. Id consequat dolore aliqua sit sunt cillum incididunt aliqua sint dolore quis eiusmod elit aliquip. Nisi aute mollit ipsum dolore eiusmod incididunt. Tempor officia culpa consequat laboris culpa. Lorem pariatur irure minim dolore elit enim elit esse nostrud.\n\nAmet ipsum officia sunt aliqua laborum mollit Lorem et. Est consectetur eu nostrud minim minim irure occaecat sint ea. Ut magna aliqua labore exercitation. Minim sint excepteur cupidatat esse quis do duis ad. Est magna in velit ut cupidatat mollit non do id ea ut non.\n\nDolor dolor elit fugiat aliqua aliquip in officia. Elit et cupidatat commodo laboris nostrud fugiat non cupidatat velit pariatur. Labore anim velit proident nisi ullamco. Reprehenderit adipisicing nisi laboris ut.\n\nBest Regards,\nHaney Gibson', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: 'e766ddd9-e9ea-499c-bb10-72fa57a03059', + type: 'mail', + from: { + avatar: 'images/avatars/female-02.jpg', + contact: 'Freda Frank ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Wed Nov 21 2018 01:26:11 GMT+0000 (UTC)').toISOString(), + subject: 'Magna sint dolor sit laborum enim', + content: + 'Hello Brian,\n\nExcepteur occaecat dolor ex Lorem sunt velit enim eiusmod et fugiat mollit fugiat labore proident. Lorem mollit exercitation labore dolor ullamco proident non irure aliquip. Cillum laboris sit incididunt nulla dolor. Irure mollit mollit irure incididunt eiusmod consequat. Mollit in nisi enim veniam culpa mollit id dolor.\n\nSunt cillum esse est ut exercitation voluptate Lorem. Duis labore ut ullamco non ea do minim in cupidatat magna officia. Ullamco qui occaecat esse magna Lorem occaecat veniam in. Deserunt voluptate do voluptate nulla fugiat laborum officia Lorem mollit quis. Eiusmod sit pariatur qui sint ea irure cillum officia deserunt. Eiusmod irure in enim ullamco Lorem aliqua ad.\n\nDuis deserunt in nisi aute excepteur exercitation minim. Tempor pariatur qui irure laborum in incididunt. Dolor eu aliqua enim cupidatat ad aliquip culpa ipsum consequat consequat. Lorem nostrud deserunt officia commodo. Dolore officia ipsum cupidatat tempor ex occaecat pariatur anim ea consectetur incididunt. Veniam ad pariatur pariatur dolor enim tempor sint aliqua quis minim aliquip magna mollit eiusmod.\n\nKind Regards,\nFreda Frank', + attachments: [], + starred: false, + important: false, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '03693a62-d6bd-47c8-8f8e-112f21042722', + type: 'mail', + from: { + avatar: 'images/avatars/male-17.jpg', + contact: 'Burns White ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Mon May 14 2018 20:06:57 GMT+0000 (UTC)').toISOString(), + subject: 'Veniam cillum ad proident incididunt nisi exercitation est', + content: + 'Hi Brian,\n\nEsse reprehenderit duis labore aliqua magna mollit ut aliquip. Fugiat aliquip ipsum aliqua laborum ipsum sint nisi proident laborum consectetur dolor veniam commodo. Ea veniam eu laborum nulla non voluptate incididunt nostrud nulla fugiat velit. Nulla aliqua sit eu amet mollit. Aute laboris excepteur ut quis elit non anim aliqua ut et ea cillum consequat ex. Pariatur tempor esse excepteur ea nostrud incididunt culpa elit aliquip proident tempor non id consectetur. Duis eiusmod sint deserunt tempor mollit sint do ad labore adipisicing.\n\nElit enim ipsum mollit pariatur in officia non qui est ipsum dolore Lorem nostrud nulla. Adipisicing aliquip enim ullamco minim in sint aliqua magna enim adipisicing. Reprehenderit ea nulla velit nostrud veniam qui est elit dolore. Et dolor labore commodo veniam aliquip laborum consequat voluptate fugiat et eu. Veniam minim sunt ex laborum. Aliquip nostrud minim pariatur nostrud eiusmod mollit minim irure aliqua. Minim id Lorem magna nostrud consequat irure.\n\nDuis id deserunt eiusmod adipisicing fugiat in irure sit aliqua ipsum velit. Aute aliquip Lorem pariatur cillum fugiat labore et. Ipsum commodo sunt enim eiusmod adipisicing exercitation elit adipisicing culpa laborum cupidatat laboris duis. Mollit officia consectetur voluptate nisi mollit. Magna ipsum sint sint est culpa in magna ad eu quis officia.\n\nBest Regards,\nBurns White', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: 'c59f5ea4-4f5d-4b9e-9c3c-a996b18fd98c', + type: 'mail', + from: { + avatar: 'images/avatars/male-18.jpg', + contact: 'Fischer Cervantes ', + }, + to: 'me ', + date: new Date('Thu Dec 13 2018 03:52:15 GMT+0000 (UTC)').toISOString(), + subject: + 'Amet aute tempor sit tempor minim nulla dolor commodo aute eu', + content: + 'Hi Brian,\n\nCulpa nulla est deserunt ut. Culpa eu velit occaecat ut sint voluptate. Nostrud sint officia pariatur eiusmod commodo laborum. Proident consequat nostrud anim qui velit quis.\n\nVoluptate occaecat pariatur minim eu culpa ex sit nostrud. Amet id proident consequat commodo ullamco deserunt eu occaecat anim do exercitation sit quis non. Voluptate eiusmod aute cillum culpa sit. Dolore cillum ea ex reprehenderit occaecat ullamco. Est nulla minim est do adipisicing id in et nostrud voluptate. Proident eiusmod enim cupidatat minim. Amet minim sunt incididunt pariatur amet cupidatat eu exercitation officia laborum.\n\nId elit minim exercitation occaecat exercitation. Cupidatat officia duis duis id nisi nostrud quis dolor officia. Dolore occaecat aliqua eiusmod mollit commodo officia sunt sit laboris nisi excepteur irure duis fugiat.\n\nKind Regards,\nFischer Cervantes', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: '9ed5d4c1-819a-4719-88d7-cd7b08b2228d', + type: 'mail', + from: { + avatar: 'images/avatars/male-19.jpg', + contact: 'Cervantes Reyes ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Tue Oct 16 2018 11:06:27 GMT+0000 (UTC)').toISOString(), + subject: + 'Et eu adipisicing aliqua nisi minim commodo anim aliqua aliquip', + content: + 'Hi Brian,\n\nDo irure id voluptate occaecat quis eiusmod. Nulla non incididunt do ut excepteur proident nulla aliqua minim ex. Enim et elit eiusmod ex dolor aliqua et cupidatat consectetur nulla consectetur et fugiat cillum. Amet ea laboris non duis voluptate id fugiat voluptate et sit magna fugiat quis non.\n\nAmet tempor tempor ut eu proident deserunt. Velit exercitation irure sunt mollit veniam exercitation eiusmod nisi do velit labore sit. In exercitation et Lorem pariatur dolor aliquip aliquip occaecat. Consectetur aliqua ea voluptate aliquip consectetur do tempor sunt sint elit. Exercitation ipsum cupidatat qui exercitation cillum non cupidatat occaecat. Cupidatat consequat ut quis ad incididunt proident culpa qui minim.\n\nEu adipisicing voluptate amet occaecat amet est qui eu nisi aliqua. Quis labore in minim esse deserunt labore nulla qui dolor nulla id veniam nulla. Nulla aliquip pariatur id sunt fugiat laboris incididunt sunt ipsum. Sint dolor non nulla aliqua ea dolor officia veniam officia deserunt duis deserunt. Veniam esse consectetur deserunt excepteur laborum aliquip qui labore. Reprehenderit labore ex do nostrud esse.\n\nBest Regards,\nCervantes Reyes', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'a769720b-0c64-483c-925c-5d747c61bff4', + type: 'mail', + from: { + avatar: 'images/avatars/male-20.jpg', + contact: 'Cooke Whitney ', + }, + to: 'me ', + date: new Date('Sat Jul 28 2018 14:16:37 GMT+0000 (UTC)').toISOString(), + subject: + 'Est veniam aliquip culpa deserunt commodo ad laboris ad ullamco', + content: + 'Hi Brian,\n\nVoluptate consequat ullamco eiusmod deserunt eu laborum ullamco Lorem minim eiusmod est eu elit esse. Enim ipsum irure occaecat magna. Esse labore irure incididunt mollit dolor veniam ut magna aliquip. In ex consequat culpa nisi in exercitation. Sunt tempor quis deserunt laborum nulla ad.\n\nVoluptate mollit nostrud consectetur amet enim dolor. Consequat deserunt eiusmod incididunt cupidatat ex anim aliquip minim mollit incididunt tempor. Quis quis deserunt et tempor sunt laboris quis non enim veniam nisi nulla. Ea adipisicing incididunt laboris incididunt exercitation voluptate exercitation ipsum velit duis aute cupidatat labore. Nulla ea non est deserunt proident deserunt qui irure quis enim occaecat cupidatat. Sint veniam consequat Lorem dolore commodo aliqua et. Ad commodo qui ad ea consectetur non dolore dolor.\n\nAnim exercitation id do laborum quis laborum elit officia cupidatat sunt consectetur officia ex excepteur. Cillum cupidatat et consectetur ex aliquip anim Lorem eiusmod nostrud reprehenderit. Magna ullamco qui incididunt dolore anim ad laborum. Laborum occaecat laboris consequat et esse nostrud elit anim amet incididunt aliquip mollit ad proident. Voluptate pariatur ex adipisicing dolore dolor adipisicing cupidatat tempor amet duis dolore sunt consequat qui. Mollit do ullamco enim nulla dolore proident in. Ipsum irure cillum irure aute culpa duis eu est dolore est laborum.\n\nKind Regards,\nCooke Whitney', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '0197c436-2ef3-424d-b546-8b7f49186e15', + labels: [], + }, + { + id: 'e6b83f13-c25e-4355-913f-54d93d8393f6', + type: 'mail', + from: { + avatar: 'images/avatars/female-03.jpg', + contact: 'Lee Lloyd ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Thu Oct 18 2018 02:56:36 GMT+0000 (UTC)').toISOString(), + subject: 'Culpa incididunt amet sunt ipsum ad nostrud exercitation ea', + content: + 'Hi Brian,\n\nDolore voluptate ea id aliquip qui cillum. Adipisicing velit esse et sunt culpa quis velit mollit culpa mollit nostrud. Nulla ad elit cupidatat ex id velit proident aliquip sit irure aliquip exercitation exercitation. Occaecat proident reprehenderit consectetur tempor velit amet cupidatat.\n\nAd est sunt commodo occaecat cillum fugiat minim reprehenderit minim nulla id velit. Ullamco enim ullamco qui eu ut est qui dolore reprehenderit non tempor excepteur. Fugiat irure in pariatur qui incididunt minim cillum. Aliquip incididunt reprehenderit cillum laborum eiusmod sint aute sint. Deserunt pariatur deserunt elit ut velit cupidatat. Ad deserunt ea laborum reprehenderit laboris ut pariatur labore.\n\nLabore ullamco irure mollit aliqua irure officia est excepteur ut. Dolore amet ut id fugiat deserunt reprehenderit pariatur anim. Dolor est amet ipsum labore fugiat culpa minim anim aliqua. Officia ad duis est irure in consequat nostrud duis. Irure sit quis ad nisi qui adipisicing labore consectetur consequat duis eiusmod nisi. Non dolore tempor exercitation nulla nisi Lorem eu adipisicing aliqua dolore qui non. Enim non exercitation occaecat reprehenderit adipisicing dolore laboris eiusmod.\n\nCheers!\nLee Lloyd', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: '5c5c4ba7-542b-46b0-b0ce-976f5189d72c', + type: 'mail', + from: { + avatar: 'images/avatars/male-01.jpg', + contact: 'Benson Shields ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sun Apr 01 2018 20:39:05 GMT+0000 (UTC)').toISOString(), + subject: 'Sit incididunt ad tempor veniam duis', + content: + 'Dear Brian,\n\nAnim dolor exercitation magna qui incididunt ullamco enim. Voluptate qui laborum tempor ex minim eu dolore officia Lorem do pariatur laborum. Esse et ullamco reprehenderit nisi anim nostrud est deserunt.\n\nQuis qui commodo exercitation minim ea nisi. Aliqua culpa ad aliqua velit eiusmod do duis ex commodo eiusmod. Laborum nostrud nulla qui non reprehenderit voluptate cillum mollit exercitation anim ipsum cillum.\n\nEiusmod nisi ullamco ex ut velit. Ipsum sint dolor minim aute minim mollit ullamco voluptate magna nulla sint. Pariatur Lorem pariatur velit laboris tempor excepteur tempor reprehenderit culpa Lorem.\n\nKind Regards,\nBenson Shields', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [], + }, + { + id: '36abfef2-f86a-4c9e-99de-1869f0b3e71b', + type: 'mail', + from: { + avatar: 'images/avatars/male-02.jpg', + contact: 'Emerson Whitehead ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sun Jun 24 2018 11:19:49 GMT+0000 (UTC)').toISOString(), + subject: 'Esse ea ut est excepteur', + content: + 'Hey Brian,\n\nEst consequat aute laborum voluptate do aliqua cillum non excepteur nostrud culpa enim veniam nulla. Proident et nisi consequat nisi labore incididunt eiusmod fugiat. Nisi sint ut sint proident culpa pariatur ipsum quis dolor voluptate. Elit proident laboris eu elit. Id nisi dolor quis nostrud cillum quis ut ad quis velit eiusmod.\n\nMinim reprehenderit ullamco culpa cupidatat voluptate ut sunt. Exercitation sit dolore ullamco commodo exercitation cupidatat nulla officia Lorem exercitation officia minim. Reprehenderit ex incididunt magna id culpa incididunt ex reprehenderit ea veniam culpa id occaecat.\n\nDo esse ut non laborum aute. Aute laborum tempor eiusmod id amet anim. Quis exercitation id fugiat deserunt in do irure duis. Id ad ea eiusmod magna excepteur nulla.\n\nCheers!\nEmerson Whitehead', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'b1a0ab26-6c86-4888-b2f1-69928b3ca718', + type: 'mail', + from: { + avatar: 'images/avatars/female-04.jpg', + contact: 'Annabelle Greene ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Aug 30 2018 03:24:13 GMT+0000 (UTC)').toISOString(), + subject: 'Commodo reprehenderit laborum nostrud culpa et aliquip', + content: + 'Hey Brian,\n\nConsequat amet proident esse laboris nisi excepteur mollit enim ad ipsum. Eiusmod culpa anim magna laboris amet veniam qui. Mollit minim elit tempor in nostrud incididunt pariatur. Ea dolor laboris cupidatat in aliquip elit proident ipsum ad. Ad do pariatur do magna eu voluptate eu qui commodo consectetur exercitation pariatur eu.\n\nSit est nisi tempor eiusmod esse laboris reprehenderit laborum quis incididunt duis amet esse. Lorem do do nulla est. Deserunt magna laborum do pariatur excepteur amet laboris anim sunt nulla. Veniam aliqua non adipisicing id cillum laborum aliqua. Reprehenderit deserunt amet nulla proident. Voluptate aliqua occaecat ex ut deserunt amet voluptate quis id pariatur excepteur incididunt. Magna ex nulla minim magna id cillum nisi id quis culpa consequat ea exercitation.\n\nIncididunt et aliqua officia sit nulla anim commodo est. Irure commodo veniam quis qui ad sit labore mollit in officia non incididunt in tempor. Tempor nulla velit excepteur esse. Duis enim sunt irure consectetur excepteur fugiat duis pariatur exercitation cupidatat commodo. Do cupidatat et labore magna dolor aliquip aute tempor aute. Et sit fugiat commodo eiusmod qui ex minim dolor eu do minim qui veniam.\n\nBest Regards,\nAnnabelle Greene', + attachments: [], + starred: false, + important: true, + unread: false, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '87cf5188-34dc-4947-b780-48c7fb6b6b23', + type: 'mail', + from: { + avatar: 'images/avatars/male-03.jpg', + contact: 'Dawson Lewis ', + }, + to: 'me ', + date: new Date('Fri Aug 03 2018 03:03:12 GMT+0000 (UTC)').toISOString(), + subject: 'Ipsum duis sint incididunt nulla in labore nulla', + content: + 'Hello Brian,\n\nAdipisicing quis deserunt consectetur proident eiusmod velit irure minim dolore sunt aliqua aliqua. Dolore excepteur ea commodo consectetur. Qui veniam est do cillum non excepteur adipisicing excepteur quis sit. Do cupidatat consectetur pariatur nulla exercitation dolor exercitation mollit. Elit culpa ea mollit laboris anim nisi id velit. Elit esse ad commodo dolor culpa nostrud consequat Lorem laboris pariatur et esse. Nisi elit esse ad cupidatat commodo eiusmod irure aliquip sit deserunt id anim tempor.\n\nIrure deserunt dolore nisi magna ipsum ut qui amet elit consectetur ex pariatur. Aliquip anim nostrud enim exercitation commodo eiusmod mollit qui id nulla. Lorem aute exercitation commodo enim veniam ea aute laborum consequat sunt proident eu. Quis deserunt incididunt mollit adipisicing nostrud laboris. Laborum elit velit proident aliquip ex aliqua dolore magna cillum adipisicing nisi cillum sunt esse.\n\nCillum eu id cillum eu incididunt adipisicing pariatur est sint minim voluptate Lorem Lorem excepteur. Aliqua ipsum non occaecat aute eiusmod deserunt aliquip. Sit incididunt cupidatat pariatur exercitation laborum id qui ut pariatur deserunt fugiat occaecat occaecat incididunt. Amet ad do esse et aliquip magna ullamco commodo deserunt exercitation irure. Consequat dolor magna mollit laboris pariatur laboris.\n\nBest Regards,\nDawson Lewis', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '8749e3bc-24b3-43f3-997b-ee0b5bd7a442', + type: 'mail', + from: { + avatar: 'images/avatars/male-04.jpg', + contact: 'Cole Dotson ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Tue Sep 04 2018 09:09:08 GMT+0000 (UTC)').toISOString(), + subject: 'Sint quis veniam tempor sint', + content: + 'Hi Brian,\n\nReprehenderit magna Lorem voluptate mollit irure nulla duis est adipisicing. Velit labore ullamco sit dolore. Officia magna est sunt esse veniam eiusmod nostrud laboris eiusmod ullamco nostrud cupidatat veniam.\n\nAliqua veniam magna laborum laboris officia. Excepteur occaecat nisi culpa anim amet dolore culpa culpa laborum veniam deserunt esse sunt. Nostrud tempor adipisicing sit eiusmod dolore.\n\nUt adipisicing labore officia ipsum qui officia aute. Qui in et quis ut qui labore irure. Minim voluptate qui occaecat est. Laborum aliquip enim elit incididunt eiusmod ea sit id in. Qui nostrud ad nostrud deserunt incididunt aute in aliquip.\n\nBest Regards,\nCole Dotson', + attachments: [], + starred: false, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '108ca3fa-a4dd-4988-a7ea-659ab4446050', + type: 'mail', + from: { + avatar: 'images/avatars/male-05.jpg', + contact: 'Bernard Cunningham ', + }, + to: 'me ', + date: new Date('Tue Aug 21 2018 08:51:03 GMT+0000 (UTC)').toISOString(), + subject: + 'Consequat Lorem fugiat et veniam ad veniam proident excepteur laborum', + content: + 'Dear Brian,\n\nEu voluptate exercitation nulla aliqua id laboris ipsum voluptate nulla ea laboris. Magna exercitation reprehenderit mollit velit irure minim elit officia eiusmod reprehenderit non quis. Esse sunt non nisi id irure commodo incididunt amet.\n\nAdipisicing quis mollit velit ullamco enim ad laborum ex dolor ut culpa exercitation sit commodo. Amet eu et ullamco ut elit anim nulla fugiat sint. Laborum tempor incididunt laboris id pariatur velit excepteur officia nostrud mollit occaecat sit. Nulla do fugiat tempor quis reprehenderit fugiat aute. Dolor laboris amet do anim occaecat sunt in duis reprehenderit cupidatat mollit consequat nisi.\n\nUllamco ad minim dolore excepteur amet ullamco quis esse officia voluptate. Ipsum ex dolore labore enim. Cupidatat cillum exercitation cupidatat id eu esse aute tempor ut qui sit.\n\nCheers!\nBernard Cunningham', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: '63a362be-4ea7-4cc1-985f-5202db9c1370', + type: 'mail', + from: { + avatar: 'images/avatars/male-06.jpg', + contact: 'Edwards Mcconnell ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Fri Jul 20 2018 05:22:32 GMT+0000 (UTC)').toISOString(), + subject: + 'Amet ipsum voluptate voluptate dolore proident voluptate officia cillum adipisicing tempor tempor ad anim', + content: + 'Hey Brian,\n\nAliqua Lorem fugiat in fugiat commodo laborum sit mollit Lorem elit. Nulla incididunt sint nostrud magna labore elit quis ex. Ex dolore labore tempor cillum magna tempor est exercitation in proident. Dolor est esse consectetur veniam sint proident enim mollit.\n\nQui eiusmod laborum veniam officia quis nisi cillum dolor cupidatat magna. Quis exercitation excepteur incididunt duis laboris ex Lorem laborum excepteur adipisicing. Fugiat exercitation reprehenderit veniam minim occaecat. Excepteur fugiat irure magna aliquip ut amet quis fugiat consectetur ea. Commodo est fugiat ea et labore dolore ullamco nulla excepteur officia ea. Lorem sunt officia pariatur ullamco sunt commodo fugiat enim. Consectetur amet duis et deserunt elit pariatur eiusmod amet excepteur fugiat dolore aliqua eu.\n\nLabore velit ea non elit esse commodo fugiat. Culpa eiusmod consequat sint laboris. Irure proident non laboris duis nisi.\n\nBest Regards,\nEdwards Mcconnell', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: 'ce206b7e-bbd0-4cd1-b69a-a8d4ef5b10bf', + type: 'mail', + from: { + avatar: 'images/avatars/female-05.jpg', + contact: 'Lizzie Sanders ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Fri Sep 07 2018 01:29:31 GMT+0000 (UTC)').toISOString(), + subject: 'Sint enim elit Lorem laboris', + content: + 'Dear Brian,\n\nNostrud Lorem sit dolore eiusmod culpa ut deserunt do. Esse nulla nostrud cupidatat aliquip ut veniam velit cillum amet cillum ea culpa culpa in. Pariatur eu duis adipisicing sint velit eu duis ex officia enim nulla. Sunt fugiat incididunt et id nulla ut ea in.\n\nCillum id ea nisi consectetur nostrud adipisicing magna incididunt ipsum reprehenderit. Exercitation labore nisi magna fugiat officia culpa id commodo eu. Ad ullamco amet pariatur deserunt elit et dolore quis cillum laboris Lorem dolore labore laboris.\n\nEst mollit aliquip labore ad duis quis mollit sunt cillum cupidatat excepteur. Ad dolor cupidatat incididunt deserunt. Ullamco id sunt et ad nisi Lorem irure. Aliquip enim occaecat velit laboris et ullamco sint dolore anim. Proident nisi nulla labore enim dolor. Ipsum eu qui nisi minim aliqua ullamco exercitation. In sint id pariatur id aliqua velit reprehenderit consequat aliquip.\n\nCheers!\nLizzie Sanders', + attachments: [], + starred: false, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, + { + id: 'f7c2e821-b2e2-4103-bb20-ddcd3a42dc7c', + type: 'mail', + from: { + avatar: 'images/avatars/female-06.jpg', + contact: 'Elise Hicks ', + }, + to: 'me ', + cc: [ + 'Graham Belltower ', + 'Julie T. ', + ], + date: new Date('Sun Mar 04 2018 15:45:07 GMT+0000 (UTC)').toISOString(), + subject: + 'Cillum proident non officia mollit nulla dolor eiusmod et aliquip laboris ut adipisicing dolor deserunt', + content: + 'Hi Brian,\n\nVelit proident et qui quis enim. Aute cillum ad ipsum esse nulla. Enim elit quis laborum id excepteur non consectetur ut incididunt enim adipisicing minim est. Dolor pariatur pariatur est cillum consectetur eu do deserunt labore duis incididunt et. Magna laboris labore velit velit ad aliquip magna.\n\nLaboris occaecat duis aliqua culpa culpa culpa quis eu et dolore. Quis irure mollit irure sint fugiat. Ea elit adipisicing incididunt cillum proident esse esse tempor nulla laborum incididunt reprehenderit. Sit minim laborum dolor magna sunt pariatur. Voluptate ullamco exercitation deserunt ea consequat aliqua Lorem non velit irure et adipisicing labore.\n\nVoluptate id exercitation eiusmod mollit et commodo sit consequat minim id. Consectetur eiusmod reprehenderit veniam elit dolor qui quis occaecat nisi ut commodo excepteur. Minim do ad veniam ullamco ea magna occaecat velit. Non do ea officia cupidatat ex proident veniam nostrud. Non occaecat laboris ad est quis ad. Commodo non aliquip nisi ullamco ullamco consequat eiusmod aliqua est sunt incididunt commodo et nostrud.\n\nCheers!\nElise Hicks', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: 'd8ca28a0-7fb7-4cd4-9058-3a867f841f76', + type: 'mail', + from: { + avatar: 'images/avatars/female-07.jpg', + contact: 'Sherri Roth ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Thu Dec 20 2018 09:37:24 GMT+0000 (UTC)').toISOString(), + subject: 'Ex laboris et sunt ex aute aute nisi', + content: + 'Hey Brian,\n\nLaboris eu incididunt reprehenderit eiusmod. Non ad tempor fugiat aliquip aliquip ullamco deserunt deserunt occaecat Lorem. Esse ut velit labore magna nostrud do eu fugiat do adipisicing fugiat fugiat id in. Reprehenderit magna aute sunt proident anim nostrud ex Lorem.\n\nDolor proident et quis ea anim sit deserunt ea non nisi. Ullamco fugiat proident consectetur qui reprehenderit incididunt anim fugiat pariatur eiusmod quis quis. Amet anim veniam labore aliquip est occaecat do magna consectetur mollit fugiat. Ut fugiat eu deserunt mollit mollit cupidatat.\n\nNisi culpa et magna est officia duis laboris adipisicing ullamco pariatur sunt nulla aute proident. Ex incididunt veniam fugiat do proident ullamco tempor qui eu qui consequat anim. Commodo minim consectetur excepteur amet in sint adipisicing cillum tempor sint et nulla. Cupidatat ut commodo esse labore anim.\n\nCheers!\nSherri Roth', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + ], + }, + { + id: 'b48be636-410c-485a-9442-7de7ce807dc2', + type: 'mail', + from: { + avatar: 'images/avatars/male-07.jpg', + contact: 'Skinner Hawkins ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sun Jun 10 2018 07:50:01 GMT+0000 (UTC)').toISOString(), + subject: + 'Eu cillum amet dolore labore voluptate qui mollit ad anim ipsum laborum eiusmod aliquip', + content: + 'Hi Brian,\n\nFugiat nisi eu aliquip do elit irure enim consectetur officia consequat. Quis eiusmod minim sint veniam quis dolor sit excepteur officia reprehenderit. Aute ex ea eu eiusmod. Consectetur velit dolore laboris proident ex. Enim sint dolore adipisicing occaecat et magna quis. Enim nostrud nisi sunt deserunt.\n\nOccaecat laborum voluptate quis culpa duis cillum excepteur velit ullamco duis nisi. Nulla cillum ea Lorem reprehenderit. Ea proident deserunt mollit esse pariatur est duis aute Lorem. Id deserunt nulla elit velit veniam ut consectetur Lorem exercitation do laborum nisi Lorem.\n\nVelit sint exercitation et ullamco ipsum deserunt irure. Consectetur mollit aliqua duis commodo laboris sit consequat laborum mollit aliquip anim. Occaecat enim quis in ullamco voluptate dolore enim culpa Lorem est consectetur deserunt tempor labore. Do non ex irure dolor elit ea Lorem duis esse sit eu fugiat eiusmod.\n\nCheers!\nSkinner Hawkins', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: 'efe990eb-6559-48a6-a909-320c465de739', + type: 'mail', + from: { + avatar: 'images/avatars/female-08.jpg', + contact: 'Velma Ellison ', + }, + to: 'me ', + date: new Date('Thu Nov 01 2018 10:59:46 GMT+0000 (UTC)').toISOString(), + subject: 'Ex duis cupidatat qui velit', + content: + 'Hey Brian,\n\nSint labore adipisicing consequat ipsum. Proident aute et reprehenderit sint laborum nulla dolor. Dolor commodo consectetur nulla id reprehenderit veniam enim culpa ad irure esse Lorem amet. Tempor laboris aute ea sint. Elit laboris eu aliquip tempor eu Lorem eu ex.\n\nMagna dolore officia in excepteur. Reprehenderit in ipsum ea ex voluptate reprehenderit et aliquip commodo deserunt excepteur nisi reprehenderit quis. Consectetur do mollit non nisi exercitation elit anim laboris elit cillum excepteur. Veniam qui deserunt culpa enim esse eu Lorem. Est in consequat cupidatat elit in nisi deserunt.\n\nProident consequat ea nisi eiusmod esse incididunt exercitation. Consequat labore veniam non elit duis aute eiusmod labore est irure. Aliquip velit minim nisi qui est. Consequat ea dolor nostrud incididunt. Nulla commodo consectetur occaecat eu nisi ullamco cillum culpa ea magna. Eiusmod quis in ex veniam duis esse do. Laboris quis mollit mollit ex nulla officia irure pariatur qui aute consectetur ad esse.\n\nCheers!\nVelma Ellison', + attachments: [], + starred: true, + important: false, + unread: false, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + ], + }, + { + id: 'd2bc3670-63f7-47c3-9d3d-4998c716f04a', + type: 'mail', + from: { + avatar: 'images/avatars/female-09.jpg', + contact: 'Tamara Fitzgerald ', + }, + to: 'me ', + date: new Date('Sat Feb 24 2018 15:37:16 GMT+0000 (UTC)').toISOString(), + subject: + 'Et esse sit eiusmod dolore eiusmod ad sit ipsum adipisicing ut esse', + content: + 'Dear Brian,\n\nIn exercitation pariatur id occaecat reprehenderit exercitation ullamco nostrud consequat nostrud anim labore reprehenderit. Pariatur ea amet eiusmod consequat aliquip culpa aute. Officia elit non nulla ullamco aliquip est nulla quis nostrud consequat irure.\n\nFugiat nisi labore excepteur non mollit duis. Irure voluptate fugiat duis ullamco exercitation cupidatat est ullamco culpa. Quis nisi nostrud nisi non commodo veniam Lorem officia proident fugiat elit exercitation consectetur. Cupidatat cupidatat mollit amet nisi voluptate et ea sint sint. Excepteur ad aute reprehenderit nisi dolore sint eu fugiat consequat nulla proident ipsum ad voluptate. Ea officia aute incididunt commodo consectetur aliquip sint. Irure veniam ipsum anim incididunt aliquip est enim consequat anim cillum veniam laborum enim laborum.\n\nAnim non eiusmod elit id cillum minim minim qui amet sint. Incididunt ullamco exercitation consequat ipsum sit eiusmod minim dolore sint laborum labore. Velit incididunt nulla consectetur duis duis. Sit labore duis nostrud tempor. Elit excepteur nostrud adipisicing eu quis ex. Aute aliquip esse laborum irure in officia qui voluptate laboris magna reprehenderit.\n\nKind Regards,\nTamara Fitzgerald', + attachments: [], + starred: true, + important: true, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: ['b167d3c4-f6ed-4ea6-9579-a12f95a9d76e'], + }, + { + id: '07b4d696-7657-4535-9838-3efb42355cbb', + type: 'mail', + from: { + avatar: 'images/avatars/male-08.jpg', + contact: 'Duncan Gilmore ', + }, + to: 'me ', + date: new Date('Mon Dec 31 2018 08:15:40 GMT+0000 (UTC)').toISOString(), + subject: 'Ipsum non ad commodo dolor enim labore ullamco', + content: + 'Hey Brian,\n\nDuis commodo commodo exercitation ex incididunt fugiat incididunt duis ex. Proident tempor nulla culpa consequat non est incididunt amet ipsum anim. Non ipsum irure consectetur nisi exercitation. Nostrud occaecat ullamco ad et tempor magna sint ea minim duis consectetur aute velit incididunt. Ad amet exercitation consectetur mollit proident minim anim excepteur nostrud.\n\nEt in nulla laboris minim ex excepteur culpa exercitation officia labore nostrud quis. Est officia velit ullamco aute consectetur Lorem consectetur voluptate qui eu. Elit non nulla laboris enim in esse quis. Pariatur ullamco cupidatat cupidatat non et anim in dolor magna quis Lorem dolore et. Sit ullamco cillum reprehenderit eu. Ut id ipsum duis occaecat occaecat.\n\nFugiat excepteur et aute magna fugiat ut consequat adipisicing quis deserunt id sint occaecat. Ut cupidatat est nisi fugiat enim laborum. Nostrud est nisi occaecat ut Lorem.\n\nKind Regards,\nDuncan Gilmore', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [], + }, + { + id: 'b8424db5-c607-4b9a-b88f-78c54343a342', + type: 'mail', + from: { + avatar: 'images/avatars/female-10.jpg', + contact: 'Betty Dean ', + }, + to: 'me ', + date: new Date('Thu Aug 16 2018 06:17:15 GMT+0000 (UTC)').toISOString(), + subject: + 'Laborum magna cupidatat qui sint proident aliquip ut commodo aute sint', + content: + 'Dear Brian,\n\nConsequat aliquip ut laboris non velit dolor fugiat. Nisi ut laborum amet occaecat proident deserunt excepteur sunt occaecat pariatur sint ullamco fugiat aliquip. Ea excepteur commodo magna ut deserunt. Reprehenderit eu quis nisi esse eiusmod ut ullamco. Esse est pariatur id labore anim cillum dolore nulla esse dolor eiusmod do magna est.\n\nDo fugiat dolore duis ex consequat amet sunt reprehenderit enim non dolore incididunt pariatur. Excepteur ipsum labore est cupidatat laborum do consectetur tempor ipsum eiusmod. Voluptate eiusmod nostrud occaecat nisi laboris et velit non nostrud. Nulla id commodo laboris culpa id cillum nostrud deserunt fugiat excepteur nisi irure laborum. Irure in aute ea non magna Lorem aute consequat excepteur duis occaecat cupidatat ea. Tempor Lorem ullamco ullamco occaecat ipsum duis aliqua velit labore dolore veniam. Mollit ex commodo qui esse.\n\nLabore et nostrud do dolor. Sit duis proident nulla mollit officia. Deserunt voluptate ad anim in id consectetur excepteur Lorem quis. Consectetur officia esse cillum Lorem aliqua ex sit proident qui occaecat. Deserunt magna in consectetur velit proident sint cupidatat commodo veniam sint cillum amet aliqua. Aute cillum officia culpa Lorem mollit amet culpa incididunt dolore voluptate minim. Aliqua fugiat aliquip nulla dolore elit aliqua quis veniam ullamco in adipisicing deserunt.\n\nCheers!\nBetty Dean', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + ], + }, + { + id: 'edf1399f-e829-4bde-ae5b-e03d18ad2f76', + type: 'mail', + from: { + avatar: 'images/avatars/male-09.jpg', + contact: 'Pate Gardner ', + }, + to: 'me ', + date: new Date('Sat May 26 2018 10:36:30 GMT+0000 (UTC)').toISOString(), + subject: 'Non labore sit dolor quis in qui esse velit ad sit', + content: + 'Hi Brian,\n\nMinim ea eiusmod eu cillum enim amet minim commodo reprehenderit ullamco pariatur sunt adipisicing excepteur. Laboris aute velit cillum aute laborum exercitation. Aute esse qui aliquip et proident excepteur nulla ullamco id quis culpa consectetur ea in. Aute ad dolor culpa voluptate deserunt consectetur Lorem ex est. Id consequat laborum qui elit velit. Nostrud incididunt ullamco ad aute officia adipisicing proident consectetur qui in fugiat elit. Qui sit officia amet ex occaecat irure.\n\nIncididunt sit est quis mollit ex nostrud dolore ullamco officia laboris. Reprehenderit labore anim ea tempor officia officia et duis cupidatat adipisicing dolore. Dolore exercitation minim culpa ut est magna aute adipisicing quis. Eiusmod nulla mollit nulla dolor elit aute incididunt aute officia tempor enim do. Fugiat tempor non dolore quis nisi do laborum qui.\n\nEst et cupidatat nulla laboris amet ut laboris. Lorem in esse culpa sunt laborum. Commodo est nisi ullamco esse veniam.\n\nKind Regards,\nPate Gardner', + attachments: [], + starred: true, + important: false, + unread: true, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [], + }, + { + id: 'bcc422a2-8a39-416e-8205-a5ce354ea622', + type: 'mail', + from: { + avatar: 'images/avatars/male-10.jpg', + contact: 'Lawson Kidd ', + }, + to: 'me ', + cc: ['Graham Belltower '], + date: new Date('Sun Mar 18 2018 02:18:44 GMT+0000 (UTC)').toISOString(), + subject: 'Proident non proident dolore non dolor reprehenderit', + content: + 'Hello Brian,\n\nDeserunt cillum in non et. Occaecat consequat cupidatat occaecat dolor laboris id nostrud laborum. Incididunt commodo eiusmod id irure ex amet. Aute officia ut voluptate id ex ut ex minim velit. Ullamco est pariatur et quis. Sint eiusmod labore qui minim laboris esse aliquip culpa in incididunt reprehenderit.\n\nNon aliqua anim occaecat cupidatat qui adipisicing elit et aliquip adipisicing cillum in in eu. Velit esse exercitation eiusmod ad id sunt duis voluptate sint veniam proident. Ullamco sit ut laboris minim voluptate ut velit excepteur ad. Ad aute et consequat pariatur aute in ipsum enim ea nostrud excepteur consequat est. Et magna excepteur irure do adipisicing. Id fugiat quis et deserunt sit nostrud fugiat eu do eu ullamco.\n\nVelit aliqua ea id ipsum irure exercitation. Et duis aliquip exercitation amet in minim aliqua proident nisi velit irure excepteur non eu. Eiusmod irure tempor mollit velit culpa excepteur in minim eiusmod. Duis et commodo qui elit quis anim consectetur elit reprehenderit. Labore aliqua cupidatat Lorem eu officia exercitation labore aliqua mollit magna ullamco cupidatat. Nostrud ea commodo ad ad eiusmod velit eiusmod. Laborum aliquip exercitation mollit et irure occaecat.\n\nKind Regards,\nLawson Kidd', + attachments: [], + starred: true, + important: true, + unread: false, + folder: '2fa74637-d362-4fd2-9a88-f7195a88bdde', + labels: [ + 'b167d3c4-f6ed-4ea6-9579-a12f95a9d76e', + '745cf30e-ca84-47a1-a553-b70eb630d8e7', + '8b035cb5-65c0-4ab1-bb4c-43b0e442d1f3', + 'b2d1e4e7-7cfd-4b51-ae59-217a093df754', + '184cd689-4ee4-47cf-9f8a-12233d614326', + ], + }, +]; diff --git a/src/app/mock-api/apps/notes/api.ts b/src/app/mock-api/apps/notes/api.ts new file mode 100644 index 0000000..31112e9 --- /dev/null +++ b/src/app/mock-api/apps/notes/api.ts @@ -0,0 +1,219 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiUtils } from '@angor/lib/mock-api'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; +import { + labels as labelsData, + notes as notesData, +} from 'app/mock-api/apps/notes/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class NotesMockApi { + private _labels: any[] = labelsData; + private _notes: any[] = notesData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Labels - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/notes/labels') + .reply(() => [200, cloneDeep(this._labels)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/notes/labels') + .reply(({ request }) => { + // Create a new label + const label = { + id: AngorMockApiUtils.guid(), + title: request.body.title, + }; + + // Update the labels + this._labels.push(label); + + return [200, cloneDeep(this._labels)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/notes/labels') + .reply(({ request }) => { + // Get label + const updatedLabel = request.body.label; + + // Update the label + this._labels = this._labels.map((label) => { + if (label.id === updatedLabel.id) { + return { + ...label, + title: updatedLabel.title, + }; + } + + return label; + }); + + return [200, cloneDeep(this._labels)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/notes/labels') + .reply(({ request }) => { + // Get label id + const id = request.params.get('id'); + + // Delete the label + this._labels = this._labels.filter((label) => label.id !== id); + + // Go through notes and delete the label + this._notes = this._notes.map((note) => ({ + ...note, + labels: note.labels.filter((item) => item !== id), + })); + + return [200, cloneDeep(this._labels)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Note Tasks - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/notes/tasks') + .reply(({ request }) => { + // Get note and task + let updatedNote = request.body.note; + const task = request.body.task; + + // Update the note + this._notes = this._notes.map((note) => { + if (note.id === updatedNote.id) { + // Update the tasks + if (!note.tasks) { + note.tasks = []; + } + + note.tasks.push({ + id: AngorMockApiUtils.guid(), + content: task, + completed: false, + }); + + // Update the updatedNote with the new task + updatedNote = cloneDeep(note); + + return { + ...note, + }; + } + + return note; + }); + + return [200, updatedNote]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notes - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/notes/all').reply(() => { + // Clone the labels and notes + const labels = cloneDeep(this._labels); + let notes = cloneDeep(this._notes); + + // Attach the labels to the notes + notes = notes.map((note) => ({ + ...note, + labels: note.labels.map((labelId) => + labels.find((label) => label.id === labelId) + ), + })); + + return [200, notes]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notes - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/notes') + .reply(({ request }) => { + // Get note + const note = request.body.note; + + // Add an id + note.id = AngorMockApiUtils.guid(); + + // Push the note + this._notes.push(note); + + return [200, note]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notes - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/notes') + .reply(({ request }) => { + // Get note + const updatedNote = request.body.updatedNote; + + // Update the note + this._notes = this._notes.map((note) => { + if (note.id === updatedNote.id) { + return { + ...updatedNote, + }; + } + + return note; + }); + + return [200, updatedNote]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notes - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/notes') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the note and delete it + this._notes.forEach((item, index) => { + if (item.id === id) { + this._notes.splice(index, 1); + } + }); + + // Return the response + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/notes/data.ts b/src/app/mock-api/apps/notes/data.ts new file mode 100644 index 0000000..caa5dd1 --- /dev/null +++ b/src/app/mock-api/apps/notes/data.ts @@ -0,0 +1,450 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const labels = [ + { + id: 'f47c92e5-20b9-44d9-917f-9ff4ad25dfd0', + title: 'Family', + }, + { + id: 'e2f749f5-41ed-49d0-a92a-1c83d879e371', + title: 'Work', + }, + { + id: 'b1cde9ee-e54d-4142-ad8b-cf55dafc9528', + title: 'Tasks', + }, + { + id: '6c288794-47eb-4605-8bdf-785b61a449d3', + title: 'Priority', + }, + { + id: 'bbc73458-940b-421c-8d5f-8dcd23a9b0d6', + title: 'Personal', + }, + { + id: '2dc11344-3507-48e0-83d6-1c047107f052', + title: 'Friends', + }, +]; + +export const notes = [ + { + id: '8f011ac5-b71c-4cd7-a317-857dcd7d85e0', + title: '', + content: 'Find a new company name', + tasks: null, + image: null, + reminder: null, + labels: ['e2f749f5-41ed-49d0-a92a-1c83d879e371'], + archived: false, + createdAt: now + .set({ + hour: 10, + minute: 19, + }) + .minus({ day: 98 }) + .toISO(), + updatedAt: null, + }, + { + id: 'ced0a1ce-051d-41a3-b080-e2161e4ae621', + title: '', + content: 'Send the photos of last summer to John', + tasks: null, + image: 'images/cards/14-640x480.jpg', + reminder: null, + labels: [ + 'bbc73458-940b-421c-8d5f-8dcd23a9b0d6', + 'b1cde9ee-e54d-4142-ad8b-cf55dafc9528', + ], + archived: false, + createdAt: now + .set({ + hour: 15, + minute: 37, + }) + .minus({ day: 80 }) + .toISO(), + updatedAt: null, + }, + { + id: 'd3ac02a9-86e4-4187-bbd7-2c965518b3a3', + title: '', + content: 'Update the design of the theme', + tasks: null, + image: null, + reminder: null, + labels: ['6c288794-47eb-4605-8bdf-785b61a449d3'], + archived: false, + createdAt: now + .set({ + hour: 19, + minute: 27, + }) + .minus({ day: 74 }) + .toISO(), + updatedAt: now + .set({ + hour: 15, + minute: 36, + }) + .minus({ day: 50 }) + .toISO(), + }, + { + id: '89861bd4-0144-4bb4-8b39-332ca10371d5', + title: '', + content: 'Theming support for all apps', + tasks: null, + image: null, + reminder: now + .set({ + hour: 12, + minute: 34, + }) + .plus({ day: 50 }) + .toISO(), + labels: ['e2f749f5-41ed-49d0-a92a-1c83d879e371'], + archived: false, + createdAt: now + .set({ + hour: 12, + minute: 34, + }) + .minus({ day: 59 }) + .toISO(), + updatedAt: null, + }, + { + id: 'ffd20f3c-2d43-4c6b-8021-278032fc9e92', + title: 'Gift Ideas', + content: + "Stephanie's birthday is coming and I need to pick a present for her. Take a look at the below list and buy one of them (or all of them)", + tasks: [ + { + id: '330a924f-fb51-48f6-a374-1532b1dd353d', + content: 'Scarf', + completed: false, + }, + { + id: '781855a6-2ad2-4df4-b0af-c3cb5f302b40', + content: 'A new bike helmet', + completed: true, + }, + { + id: 'bcb8923b-33cd-42c2-9203-170994fa24f5', + content: 'Necklace', + completed: false, + }, + { + id: '726bdf6e-5cd7-408a-9a4f-0d7bb98c1c4b', + content: 'Flowers', + completed: false, + }, + ], + image: null, + reminder: null, + labels: ['f47c92e5-20b9-44d9-917f-9ff4ad25dfd0'], + archived: false, + createdAt: now + .set({ + hour: 16, + minute: 4, + }) + .minus({ day: 47 }) + .toISO(), + updatedAt: null, + }, + { + id: '71d223bb-abab-4183-8919-cd3600a950b4', + title: 'Shopping list', + content: '', + tasks: [ + { + id: 'e3cbc986-641c-4448-bc26-7ecfa0549c22', + content: 'Bread', + completed: true, + }, + { + id: '34013111-ab2c-4b2f-9352-d2ae282f57d3', + content: 'Milk', + completed: false, + }, + { + id: '0fbdea82-cc79-4433-8ee4-54fd542c380d', + content: 'Onions', + completed: false, + }, + { + id: '66490222-743e-4262-ac91-773fcd98a237', + content: 'Coffee', + completed: true, + }, + { + id: 'ab367215-d06a-48b0-a7b8-e161a63b07bd', + content: 'Toilet Paper', + completed: true, + }, + ], + image: null, + reminder: now + .set({ + hour: 10, + minute: 44, + }) + .minus({ day: 35 }) + .toISO(), + labels: ['b1cde9ee-e54d-4142-ad8b-cf55dafc9528'], + archived: false, + createdAt: now + .set({ + hour: 10, + minute: 44, + }) + .minus({ day: 35 }) + .toISO(), + updatedAt: null, + }, + { + id: '11fbeb98-ae5e-41ad-bed6-330886fd7906', + title: 'Keynote Schedule', + content: '', + tasks: [ + { + id: '2711bac1-7d8a-443a-a4fe-506ef51d3fcb', + content: 'Breakfast', + completed: true, + }, + { + id: 'e3a2d675-a3e5-4cef-9205-feeccaf949d7', + content: 'Opening ceremony', + completed: true, + }, + { + id: '7a721b6d-9d85-48e0-b6c3-f927079af582', + content: 'Talk 1: How we did it!', + completed: true, + }, + { + id: 'bdb4d5cd-5bb8-45e2-9186-abfd8307e429', + content: 'Talk 2: How can you do it!', + completed: false, + }, + { + id: 'c8293bb4-8ab4-4310-bbc2-52ecf8ec0c54', + content: 'Lunch break', + completed: false, + }, + ], + image: null, + reminder: now + .set({ + hour: 11, + minute: 27, + }) + .minus({ day: 14 }) + .toISO(), + labels: [ + 'b1cde9ee-e54d-4142-ad8b-cf55dafc9528', + 'e2f749f5-41ed-49d0-a92a-1c83d879e371', + ], + archived: false, + createdAt: now + .set({ + hour: 11, + minute: 27, + }) + .minus({ day: 24 }) + .toISO(), + updatedAt: null, + }, + { + id: 'd46dee8b-8761-4b6d-a1df-449d6e6feb6a', + title: '', + content: "Organize the dad's surprise retirement party", + tasks: null, + image: null, + reminder: now + .set({ + hour: 14, + minute: 56, + }) + .minus({ day: 25 }) + .toISO(), + labels: ['f47c92e5-20b9-44d9-917f-9ff4ad25dfd0'], + archived: false, + createdAt: now + .set({ + hour: 14, + minute: 56, + }) + .minus({ day: 20 }) + .toISO(), + updatedAt: null, + }, + { + id: '6bc9f002-1675-417c-93c4-308fba39023e', + title: 'Plan the road trip', + content: '', + tasks: null, + image: 'images/cards/17-640x480.jpg', + reminder: null, + labels: [ + '2dc11344-3507-48e0-83d6-1c047107f052', + 'b1cde9ee-e54d-4142-ad8b-cf55dafc9528', + ], + archived: false, + createdAt: now + .set({ + hour: 9, + minute: 32, + }) + .minus({ day: 15 }) + .toISO(), + updatedAt: now + .set({ + hour: 17, + minute: 6, + }) + .minus({ day: 12 }) + .toISO(), + }, + { + id: '15188348-78aa-4ed6-b5c2-028a214ba987', + title: 'Office Address', + content: '933 8th Street Stamford, CT 06902', + tasks: null, + image: null, + reminder: null, + labels: ['e2f749f5-41ed-49d0-a92a-1c83d879e371'], + archived: false, + createdAt: now + .set({ + hour: 20, + minute: 5, + }) + .minus({ day: 12 }) + .toISO(), + updatedAt: null, + }, + { + id: '1dbfc685-1a0a-4070-9ca7-ed896c523037', + title: 'Tasks', + content: '', + tasks: [ + { + id: '004638bf-3ee6-47a5-891c-3be7b9f3df09', + content: 'Wash the dishes', + completed: true, + }, + { + id: '86e6820b-1ae3-4c14-a13e-35605a0d654b', + content: 'Walk the dog', + completed: false, + }, + ], + image: null, + reminder: now + .set({ + hour: 13, + minute: 43, + }) + .minus({ day: 2 }) + .toISO(), + labels: ['bbc73458-940b-421c-8d5f-8dcd23a9b0d6'], + archived: false, + createdAt: now + .set({ + hour: 13, + minute: 43, + }) + .minus({ day: 7 }) + .toISO(), + updatedAt: null, + }, + { + id: '49548409-90a3-44d4-9a9a-f5af75aa9a66', + title: '', + content: 'Dinner with parents', + tasks: null, + image: null, + reminder: null, + labels: [ + 'f47c92e5-20b9-44d9-917f-9ff4ad25dfd0', + '6c288794-47eb-4605-8bdf-785b61a449d3', + ], + archived: false, + createdAt: now + .set({ + hour: 7, + minute: 12, + }) + .minus({ day: 2 }) + .toISO(), + updatedAt: null, + }, + { + id: 'c6d13a35-500d-4491-a3f3-6ca05d6632d3', + title: '', + content: 'Re-fill the medicine cabinet', + tasks: null, + image: null, + reminder: null, + labels: [ + 'bbc73458-940b-421c-8d5f-8dcd23a9b0d6', + '6c288794-47eb-4605-8bdf-785b61a449d3', + ], + archived: true, + createdAt: now + .set({ + hour: 17, + minute: 14, + }) + .minus({ day: 100 }) + .toISO(), + updatedAt: null, + }, + { + id: 'c6d13a35-500d-4491-a3f3-6ca05d6632d3', + title: '', + content: 'Update the icons pack', + tasks: null, + image: null, + reminder: null, + labels: ['e2f749f5-41ed-49d0-a92a-1c83d879e371'], + archived: true, + createdAt: now + .set({ + hour: 10, + minute: 29, + }) + .minus({ day: 85 }) + .toISO(), + updatedAt: null, + }, + { + id: '46214383-f8e7-44da-aa2e-0b685e0c5027', + title: 'Team Meeting', + content: 'Talk about the future of the web apps', + tasks: null, + image: null, + reminder: null, + labels: [ + 'e2f749f5-41ed-49d0-a92a-1c83d879e371', + 'b1cde9ee-e54d-4142-ad8b-cf55dafc9528', + ], + archived: true, + createdAt: now + .set({ + hour: 15, + minute: 30, + }) + .minus({ day: 69 }) + .toISO(), + updatedAt: null, + }, +]; diff --git a/src/app/mock-api/apps/scrumboard/api.ts b/src/app/mock-api/apps/scrumboard/api.ts new file mode 100644 index 0000000..50dc696 --- /dev/null +++ b/src/app/mock-api/apps/scrumboard/api.ts @@ -0,0 +1,416 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api'; +import { + boards as boardsData, + cards as cardsData, + labels as labelsData, + lists as listsData, + members as membersData, +} from 'app/mock-api/apps/scrumboard/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class ScrumboardMockApi { + // Private + private _boards: any[] = boardsData; + private _cards: any[] = cardsData; + private _labels: any[] = labelsData; + private _lists: any[] = listsData; + private _members: any[] = membersData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Boards - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/scrumboard/boards') + .reply(({ request }) => { + // Clone the boards + let boards = cloneDeep(this._boards); + + // Go through the boards and inject the members + boards = boards.map((board) => ({ + ...board, + members: board.members.map((boardMember) => + this._members.find( + (member) => boardMember === member.id + ) + ), + })); + + return [200, boards]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Board - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/scrumboard/board') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the board + const board = this._boards.find((item) => item.id === id); + + // Attach the board lists + board.lists = this._lists + .filter((item) => item.boardId === id) + .sort((a, b) => a.position - b.position); + + // Grab all cards that belong to this board and attach labels to them + let cards = this._cards.filter((item) => item.boardId === id); + cards = cards.map((card) => ({ + ...card, + labels: card.labels.map((cardLabelId) => + this._labels.find((label) => label.id === cardLabelId) + ), + })); + + // Attach the board cards into corresponding lists + board.lists.forEach((list, index, array) => { + array[index].cards = cards + .filter( + (item) => + item.boardId === id && item.listId === list.id + ) + .sort((a, b) => a.position - b.position); + }); + + // Attach the board labels + board.labels = this._labels.filter( + (item) => item.boardId === id + ); + + return [200, cloneDeep(board)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ List - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/scrumboard/board/list') + .reply(({ request }) => { + // Get the list + const newList = cloneDeep(request.body.list); + + // Generate a new GUID + newList.id = AngorMockApiUtils.guid(); + + // Store the new list + this._lists.push(newList); + + return [200, newList]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ List - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/list') + .reply(({ request }) => { + // Get the list + const list = cloneDeep(request.body.list); + + // Prepare the updated list + let updatedList = null; + + // Find the list and update it + this._lists.forEach((item, index, lists) => { + if (item.id === list.id) { + // Update the list + lists[index] = assign({}, lists[index], list); + + // Store the updated list + updatedList = lists[index]; + } + }); + + return [200, updatedList]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Lists - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/lists') + .reply(({ request }) => { + // Get the lists + const lists = cloneDeep(request.body.lists); + + // Prepare the updated lists + const updatedLists = []; + + // Go through the lists + lists.forEach((item) => { + // Find the list + const index = this._lists.findIndex( + (list) => item.id === list.id + ); + + // Update the list + this._lists[index] = assign({}, this._lists[index], item); + + // Store in the updated lists + updatedLists.push(item); + }); + + return [200, updatedLists]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ List - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/scrumboard/board/list') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the list and delete it + const index = this._lists.findIndex((item) => item.id === id); + this._lists.splice(index, 1); + + // Filter out the cards that belonged to the list to delete them + this._cards = this._cards.filter((card) => card.listId !== id); + + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Card - PUT + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPut('api/apps/scrumboard/board/card') + .reply(({ request }) => { + // Get the card + const newCard = cloneDeep(request.body.card); + + // Generate a new GUID + newCard.id = AngorMockApiUtils.guid(); + + // Unshift the new card + this._cards.push(newCard); + + return [200, newCard]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Card - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/card') + .reply(({ request }) => { + // Get the id and card + const id = request.body.id; + const card = cloneDeep(request.body.card); + + // Prepare the updated card + let updatedCard = null; + + // Go through the labels and leave only ids of them + card.labels = card.labels.map((itemLabel) => itemLabel.id); + + // Find the card and update it + this._cards.forEach((item, index, cards) => { + if (item.id === id) { + // Update the card + cards[index] = assign({}, cards[index], card); + + // Store the updated card + updatedCard = cloneDeep(cards[index]); + } + }); + + // Attach the labels of the card + updatedCard.labels = updatedCard.labels.map((cardLabelId) => + this._labels.find((label) => label.id === cardLabelId) + ); + + return [200, updatedCard]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Cards - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/cards') + .reply(({ request }) => { + // Get the cards + const cards = cloneDeep(request.body.cards); + + // Prepare the updated cards + const updatedCards = []; + + // Go through the cards + cards.forEach((item) => { + // Find the card + const index = this._cards.findIndex( + (card) => item.id === card.id + ); + + // Go through the labels and leave only ids of them + item.labels = item.labels.map((itemLabel) => itemLabel.id); + + // Update the card + this._cards[index] = assign({}, this._cards[index], item); + + // Attach the labels of the card + item.labels = item.labels.map((cardLabelId) => + this._labels.find((label) => label.id === cardLabelId) + ); + + // Store in the updated cards + updatedCards.push(item); + }); + + return [200, updatedCards]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Card - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/scrumboard/board/card') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the card and delete it + const index = this._cards.findIndex((item) => item.id === id); + this._cards.splice(index, 1); + + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Card Positions - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/card/positions') + .reply(({ request }) => { + // Get the cards + const cards = request.body.cards; + + // Go through the cards + this._cards.forEach((card) => { + // Find this card's index within the cards array that comes with the request + // and assign that index as the new position number for the card + card.position = cards.findIndex( + (item) => + item.id === card.id && + item.listId === card.listId && + item.boardId === card.boardId + ); + }); + + // Clone the cards + const updatedCards = cloneDeep(this._cards); + + return [200, updatedCards]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Labels - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/scrumboard/board/labels') + .reply(({ request }) => { + // Get the board id + const boardId = request.params.get('boardId'); + + // Filter the labels + const labels = this._labels.filter( + (item) => item.boardId === boardId + ); + + return [200, cloneDeep(labels)]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Label - PUT + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPut('api/apps/scrumboard/board/label') + .reply(({ request }) => { + // Get the label + const newLabel = cloneDeep(request.body.label); + + // Generate a new GUID + newLabel.id = AngorMockApiUtils.guid(); + + // Unshift the new label + this._labels.unshift(newLabel); + + return [200, newLabel]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Label - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/scrumboard/board/label') + .reply(({ request }) => { + // Get the id and label + const id = request.body.id; + const label = cloneDeep(request.body.label); + + // Prepare the updated label + let updatedLabel = null; + + // Find the label and update it + this._labels.forEach((item, index, labels) => { + if (item.id === id) { + // Update the label + labels[index] = assign({}, labels[index], label); + + // Store the updated label + updatedLabel = labels[index]; + } + }); + + return [200, updatedLabel]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Label - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/scrumboard/board/label') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the label and delete it + const index = this._labels.findIndex((item) => item.id === id); + this._labels.splice(index, 1); + + // Get the cards that have the label + const cardsWithLabel = this._cards.filter( + (card) => card.labels.indexOf(id) > -1 + ); + + // Iterate through them and remove the label + cardsWithLabel.forEach((card) => { + card.tags.splice(card.tags.indexOf(id), 1); + }); + + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/scrumboard/data.ts b/src/app/mock-api/apps/scrumboard/data.ts new file mode 100644 index 0000000..fee98d2 --- /dev/null +++ b/src/app/mock-api/apps/scrumboard/data.ts @@ -0,0 +1,318 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const boards = [ + { + id: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Admin Dashboard', + description: 'Roadmap for the new project', + icon: 'heroicons_outline:rectangle-group', + lastActivity: now.startOf('day').minus({ day: 1 }).toISO(), + members: [ + '9c510cf3-460d-4a8c-b3be-bcc3db578c08', + 'baa88231-0ee6-4028-96d5-7f187e0f4cd5', + '18bb18f3-ea7d-4465-8913-e8c9adf6f568', + ], + }, + { + id: '0168b519-3dab-4b46-b2ea-0e678e38a583', + title: 'Weekly Planning', + description: 'Job related tasks for the week', + icon: 'heroicons_outline:calendar', + lastActivity: now.startOf('day').minus({ day: 2 }).toISO(), + members: [ + '79ebb9ee-1e57-4706-810c-03edaec8f56d', + '319ecb5b-f99c-4ee4-81b2-3aeffd1d4735', + '5bf7ed5b-8b04-46b7-b364-005958b7d82e', + 'd1f612e6-3e3b-481f-a8a9-f917e243b06e', + 'fe0fec0d-002b-406f-87ab-47eb87ba577c', + '23a47d2c-c6cb-40cc-af87-e946a9df5028', + '6726643d-e8dc-42fa-83a6-b4ec06921a6b', + '0d1eb062-13d5-4286-b8d4-e0bea15f3d56', + ], + }, + { + id: 'bc7db965-3c4f-4233-abf5-69bd70c3c175', + title: 'Personal Tasks', + description: 'Personal tasks around the house', + icon: 'heroicons_outline:home', + lastActivity: now.startOf('day').minus({ week: 1 }).toISO(), + members: ['6f6a1c34-390b-4b2e-97c8-ff0e0d787839'], + }, +]; +export const lists = [ + { + id: 'a2df7786-519c-485a-a85f-c09a61cc5f37', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + position: 65536, + title: 'To do', + }, + { + id: '83ca2a34-65af-49c0-a42e-94a34003fcf2', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + position: 131072, + title: 'In progress', + }, + { + id: 'a85ea483-f8f7-42d9-a314-3fed6aac22ab', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + position: 196608, + title: 'In review', + }, + { + id: '34cbef38-5687-4813-bd66-141a6df6d832', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + position: 262144, + title: 'Completed', + }, +]; +export const cards = [ + { + id: 'e74e66e9-fe0f-441e-a8ce-28ed6eccc48d', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: 'a2df7786-519c-485a-a85f-c09a61cc5f37', + position: 65536, + title: 'Example that showcase all of the available bits on the card with a fairly long title compared to other cards', + description: + 'Example that showcase all of the available bits on the card with a fairly long title compared to other cards. Example that showcase all of the available bits on the card with a fairly long title compared to other cards.', + labels: [ + 'e0175175-2784-48f1-a519-a1d2e397c9b3', + '51779701-818a-4a53-bc16-137c3bd7a564', + 'e8364d69-9595-46ce-a0f9-ce428632a0ac', + 'caff9c9b-a198-4564-b1f4-8b3df1d345bb', + 'f9eeb436-13a3-4208-a239-0d555960a567', + ], + dueDate: now.startOf('day').minus({ day: 10 }).toISO(), + }, + { + id: 'ed58add1-45a7-41db-887d-3ca7ee7f2719', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: 'a2df7786-519c-485a-a85f-c09a61cc5f37', + position: 131072, + title: 'Do a research about most needed admin applications', + labels: ['e0175175-2784-48f1-a519-a1d2e397c9b3'], + dueDate: null, + }, + { + id: 'cd6897cb-acfd-4016-8b53-3f66a5b5fc68', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: 'a2df7786-519c-485a-a85f-c09a61cc5f37', + position: 196608, + title: 'Implement the Project dashboard', + labels: ['caff9c9b-a198-4564-b1f4-8b3df1d345bb'], + dueDate: now.startOf('day').toISO(), + }, + { + id: '6da8747f-b474-4c9a-9eba-5ef212285500', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: 'a2df7786-519c-485a-a85f-c09a61cc5f37', + position: 262144, + title: 'Implement the Analytics dashboard', + labels: ['caff9c9b-a198-4564-b1f4-8b3df1d345bb'], + dueDate: now.startOf('day').minus({ day: 1 }).toISO(), + }, + { + id: '94fb1dee-dd83-4cca-acdd-02e96d3cc4f1', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '83ca2a34-65af-49c0-a42e-94a34003fcf2', + position: 65536, + title: 'Analytics dashboard design', + labels: ['e8364d69-9595-46ce-a0f9-ce428632a0ac'], + dueDate: null, + }, + { + id: 'fc16f7d8-957d-43ed-ba85-20f99b5ce011', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '83ca2a34-65af-49c0-a42e-94a34003fcf2', + position: 131072, + title: 'Project dashboard design', + labels: ['e8364d69-9595-46ce-a0f9-ce428632a0ac'], + dueDate: null, + }, + { + id: 'c0b32f1f-64ec-4f8d-8b11-a8dc809df331', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: 'a85ea483-f8f7-42d9-a314-3fed6aac22ab', + position: 65536, + title: 'JWT Auth implementation', + labels: ['caff9c9b-a198-4564-b1f4-8b3df1d345bb'], + dueDate: null, + }, + { + id: '532c2747-be79-464a-9897-6a682bf22b64', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '34cbef38-5687-4813-bd66-141a6df6d832', + position: 65536, + title: 'Create low fidelity wireframes', + labels: [], + dueDate: null, + }, + { + id: '1d908efe-c830-476e-9e87-d06e30d89bc2', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '34cbef38-5687-4813-bd66-141a6df6d832', + position: 131072, + title: 'Create high fidelity wireframes', + labels: [], + dueDate: now.startOf('day').minus({ day: 10 }).toISO(), + }, + { + id: 'b1da11ed-7896-4826-962d-4b7b718896d4', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '34cbef38-5687-4813-bd66-141a6df6d832', + position: 196608, + title: 'Collect information about most used admin layouts', + labels: ['e0175175-2784-48f1-a519-a1d2e397c9b3'], + dueDate: null, + }, + { + id: '3b7f3ceb-107f-42bc-a204-c268c9a56cb4', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '34cbef38-5687-4813-bd66-141a6df6d832', + position: 262144, + title: 'Do a research about latest UI trends', + labels: ['e0175175-2784-48f1-a519-a1d2e397c9b3'], + dueDate: null, + }, + { + id: 'cd7f01c5-a941-4076-8cef-37da0354e643', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + listId: '34cbef38-5687-4813-bd66-141a6df6d832', + position: 327680, + title: 'Learn more about UX', + labels: ['e0175175-2784-48f1-a519-a1d2e397c9b3'], + dueDate: null, + }, +]; +export const labels = [ + { + id: 'e0175175-2784-48f1-a519-a1d2e397c9b3', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Research', + }, + { + id: '51779701-818a-4a53-bc16-137c3bd7a564', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Wireframing', + }, + { + id: 'e8364d69-9595-46ce-a0f9-ce428632a0ac', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Design', + }, + { + id: 'caff9c9b-a198-4564-b1f4-8b3df1d345bb', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Development', + }, + { + id: 'f9eeb436-13a3-4208-a239-0d555960a567', + boardId: '2c82225f-2a6c-45d3-b18a-1132712a4234', + title: 'Bug', + }, +]; +export const members = [ + { + id: '6f6a1c34-390b-4b2e-97c8-ff0e0d787839', + name: 'Angeline Vinson', + avatar: 'images/avatars/female-01.jpg', + }, + { + id: '4ce4be48-c8c0-468d-9df8-ddfda14cdb37', + name: 'Roseann Greer', + avatar: 'images/avatars/female-02.jpg', + }, + { + id: '9c510cf3-460d-4a8c-b3be-bcc3db578c08', + name: 'Lorraine Barnett', + avatar: 'images/avatars/female-03.jpg', + }, + { + id: '7ec887d9-b01a-4057-b5dc-aaed18637cc1', + name: 'Middleton Bradford', + avatar: 'images/avatars/male-01.jpg', + }, + { + id: '74975a82-addb-427b-9b43-4d2e03331b68', + name: 'Sue Hays', + avatar: 'images/avatars/female-04.jpg', + }, + { + id: '18bb18f3-ea7d-4465-8913-e8c9adf6f568', + name: 'Keith Neal', + avatar: 'images/avatars/male-02.jpg', + }, + { + id: 'baa88231-0ee6-4028-96d5-7f187e0f4cd5', + name: 'Wilkins Gilmore', + avatar: 'images/avatars/male-03.jpg', + }, + { + id: '0d1eb062-13d5-4286-b8d4-e0bea15f3d56', + name: 'Baldwin Stein', + avatar: 'images/avatars/male-04.jpg', + }, + { + id: '5bf7ed5b-8b04-46b7-b364-005958b7d82e', + name: 'Bobbie Cohen', + avatar: 'images/avatars/female-05.jpg', + }, + { + id: '93b1a72b-e2db-4f77-82d6-272047433508', + name: 'Melody Peters', + avatar: 'images/avatars/female-06.jpg', + }, + { + id: 'd1f612e6-3e3b-481f-a8a9-f917e243b06e', + name: 'Marquez Ryan', + avatar: 'images/avatars/male-05.jpg', + }, + { + id: '79ebb9ee-1e57-4706-810c-03edaec8f56d', + name: 'Roberta Briggs', + avatar: 'images/avatars/female-07.jpg', + }, + { + id: '6726643d-e8dc-42fa-83a6-b4ec06921a6b', + name: 'Robbie Buckley', + avatar: 'images/avatars/female-08.jpg', + }, + { + id: '8af617d7-898e-4992-beda-d5ac1d7ceda4', + name: 'Garcia Whitney', + avatar: 'images/avatars/male-06.jpg', + }, + { + id: 'bcff44c4-9943-4adc-9049-08b1d922a658', + name: 'Spencer Pate', + avatar: 'images/avatars/male-07.jpg', + }, + { + id: '54160ca2-29c9-4475-88a1-31a9307ad913', + name: 'Monica Mcdaniel', + avatar: 'images/avatars/female-09.jpg', + }, + { + id: '51286603-3a43-444e-9242-f51fe57d5363', + name: 'Mcmillan Durham', + avatar: 'images/avatars/male-08.jpg', + }, + { + id: '319ecb5b-f99c-4ee4-81b2-3aeffd1d4735', + name: 'Jeoine Hebert', + avatar: 'images/avatars/female-10.jpg', + }, + { + id: 'fe0fec0d-002b-406f-87ab-47eb87ba577c', + name: 'Susanna Kline', + avatar: 'images/avatars/female-11.jpg', + }, + { + id: '23a47d2c-c6cb-40cc-af87-e946a9df5028', + name: 'Suzette Singleton', + avatar: 'images/avatars/female-12.jpg', + }, +]; diff --git a/src/app/mock-api/apps/tasks/api.ts b/src/app/mock-api/apps/tasks/api.ts new file mode 100644 index 0000000..2b6dd31 --- /dev/null +++ b/src/app/mock-api/apps/tasks/api.ts @@ -0,0 +1,292 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api/mock-api.service'; +import { AngorMockApiUtils } from '@angor/lib/mock-api/mock-api.utils'; +import { + tags as tagsData, + tasks as tasksData, +} from 'app/mock-api/apps/tasks/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class TasksMockApi { + private _tags: any[] = tagsData; + private _tasks: any[] = tasksData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Tags - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/tasks/tags') + .reply(() => [200, cloneDeep(this._tags)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/tasks/tag') + .reply(({ request }) => { + // Get the tag + const newTag = cloneDeep(request.body.tag); + + // Generate a new GUID + newTag.id = AngorMockApiUtils.guid(); + + // Unshift the new tag + this._tags.unshift(newTag); + + return [200, newTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tags - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/tasks/tag') + .reply(({ request }) => { + // Get the id and tag + const id = request.body.id; + const tag = cloneDeep(request.body.tag); + + // Prepare the updated tag + let updatedTag = null; + + // Find the tag and update it + this._tags.forEach((item, index, tags) => { + if (item.id === id) { + // Update the tag + tags[index] = assign({}, tags[index], tag); + + // Store the updated tag + updatedTag = tags[index]; + } + }); + + return [200, updatedTag]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tag - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/tasks/tag') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the tag and delete it + const index = this._tags.findIndex((item) => item.id === id); + this._tags.splice(index, 1); + + // Get the tasks that have the tag + const tasksWithTag = this._tasks.filter( + (task) => task.tags.indexOf(id) > -1 + ); + + // Iterate through them and remove the tag + tasksWithTag.forEach((task) => { + task.tags.splice(task.tags.indexOf(id), 1); + }); + + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tasks - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/apps/tasks/all').reply(() => { + // Clone the tasks + const tasks = cloneDeep(this._tasks); + + // Sort the tasks by order + tasks.sort((a, b) => a.order - b.order); + + return [200, tasks]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tasks Search - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/tasks/search') + .reply(({ request }) => { + // Get the search query + const query = request.params.get('query'); + + // Prepare the search results + let results; + + // If the query exists... + if (query) { + // Clone the tasks + let tasks = cloneDeep(this._tasks); + + // Filter the tasks + tasks = tasks.filter( + (task) => + (task.title && + task.title + .toLowerCase() + .includes(query.toLowerCase())) || + (task.notes && + task.notes + .toLowerCase() + .includes(query.toLowerCase())) + ); + + // Mark the found chars + tasks.forEach((task) => { + const re = new RegExp( + '(' + + query.replace( + /[-\/\\^$*+?.()|[\]{}]/g, + '\\$&' + ) + + ')', + 'ig' + ); + task.title = task.title.replace(re, '$1'); + }); + + // Set them as the search result + results = tasks; + } + // Otherwise, set the results to null + else { + results = null; + } + + return [200, results]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Tasks Orders - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/tasks/order') + .reply(({ request }) => { + // Get the tasks + const tasks = request.body.tasks; + + // Go through the tasks + this._tasks.forEach((task) => { + // Find this task's index within the tasks array that comes with the request + // and assign that index as the new order number for the task + task.order = tasks.findIndex( + (item: any) => item.id === task.id + ); + }); + + // Clone the tasks + const updatedTasks = cloneDeep(this._tasks); + + return [200, updatedTasks]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Task - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/apps/tasks/task') + .reply(({ request }) => { + // Get the id from the params + const id = request.params.get('id'); + + // Clone the tasks + const tasks = cloneDeep(this._tasks); + + // Find the task + const task = tasks.find((item) => item.id === id); + + return [200, task]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Task - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/apps/tasks/task') + .reply(({ request }) => { + // Generate a new task + const newTask = { + id: AngorMockApiUtils.guid(), + type: request.body.type, + title: '', + notes: null, + completed: false, + dueDate: null, + priority: 1, + tags: [], + order: 0, + }; + + // Unshift the new task + this._tasks.unshift(newTask); + + // Go through the tasks and update their order numbers + this._tasks.forEach((task, index) => { + task.order = index; + }); + + return [200, newTask]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Task - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/apps/tasks/task') + .reply(({ request }) => { + // Get the id and task + const id = request.body.id; + const task = cloneDeep(request.body.task); + + // Prepare the updated task + let updatedTask = null; + + // Find the task and update it + this._tasks.forEach((item, index, tasks) => { + if (item.id === id) { + // Update the task + tasks[index] = assign({}, tasks[index], task); + + // Store the updated task + updatedTask = tasks[index]; + } + }); + + return [200, updatedTask]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Task - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/apps/tasks/task') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Find the task and delete it + const index = this._tasks.findIndex((item) => item.id === id); + this._tasks.splice(index, 1); + + return [200, true]; + }); + } +} diff --git a/src/app/mock-api/apps/tasks/data.ts b/src/app/mock-api/apps/tasks/data.ts new file mode 100644 index 0000000..f1a8294 --- /dev/null +++ b/src/app/mock-api/apps/tasks/data.ts @@ -0,0 +1,925 @@ +/* eslint-disable */ +export const tags = [ + { + id: 'a0bf42ca-c3a5-47be-8341-b9c0bb8ef270', + title: 'Api', + }, + { + id: 'c6058d0d-a4b0-4453-986a-9d249ec230b1', + title: 'Frontend', + }, + { + id: 'd3ef4226-ef2c-43b0-a986-3e3e07f32799', + title: 'Bug', + }, + { + id: '51483dd3-cb98-4400-9128-4bd66b455807', + title: 'Backend', + }, + { + id: '91658b8a-f382-4b0c-a53f-e9390351c2c5', + title: 'Urgent', + }, + { + id: '2b884143-419a-45ca-a7f6-48f99f4e7798', + title: 'Discuss', + }, +]; +export const members = [ + { + id: '65f1c421-83c5-4cdf-99da-d97794328679', + name: 'Angeline Vinson', + avatar: 'images/avatars/female-01.jpg', + }, + { + id: '88a2a76c-0e6f-49da-b617-46d7c3b6e64d', + name: 'Roseann Greer', + avatar: 'images/avatars/female-02.jpg', + }, + { + id: '6ab7751e-6579-40af-9171-231c0fd6a993', + name: 'Lorraine Barnett', + avatar: 'images/avatars/female-03.jpg', + }, + { + id: '3e353312-6a9b-46af-adda-5061b06e806b', + name: 'Middleton Bradford', + avatar: 'images/avatars/male-01.jpg', + }, + { + id: '3a23baf7-2db8-4ef5-8d49-86d3e708dff5', + name: 'Sue Hays', + avatar: 'images/avatars/female-04.jpg', + }, + { + id: 'e62ab50e-90d3-4ed7-a911-093bb44d0c50', + name: 'Keith Neal', + avatar: 'images/avatars/male-02.jpg', + }, + { + id: '368aab1e-ebce-43ba-8925-4cf13937867b', + name: 'Wilkins Gilmore', + avatar: 'images/avatars/male-03.jpg', + }, + { + id: 'ef44b39b-3272-45f5-a15e-264c3b2d118e', + name: 'Baldwin Stein', + avatar: 'images/avatars/male-04.jpg', + }, + { + id: '7f5db993-ec36-412f-9db3-16d076a98807', + name: 'Bobbie Cohen', + avatar: 'images/avatars/female-05.jpg', + }, + { + id: 'e2c81627-a8a1-4bbc-9adc-ac4281e040d4', + name: 'Melody Peters', + avatar: 'images/avatars/female-06.jpg', + }, + { + id: 'a21ec32e-54ba-480b-afdc-d1cbe18a96fd', + name: 'Marquez Ryan', + avatar: 'images/avatars/male-05.jpg', + }, + { + id: '45e09584-1a54-40e6-8210-1de4d1c05593', + name: 'Roberta Briggs', + avatar: 'images/avatars/female-07.jpg', + }, + { + id: '6617b0a3-0ccd-44ea-af78-c6633115d683', + name: 'Robbie Buckley', + avatar: 'images/avatars/female-08.jpg', + }, + { + id: '271e6a06-0d37-433d-bc8d-607b12bcbed9', + name: 'Garcia Whitney', + avatar: 'images/avatars/male-06.jpg', + }, + { + id: '65e15136-5168-4655-8bbc-e3ad8a94bf67', + name: 'Spencer Pate', + avatar: 'images/avatars/male-07.jpg', + }, + { + id: '28dcda24-812d-4086-9638-b28bd85beecc', + name: 'Monica Mcdaniel', + avatar: 'images/avatars/female-09.jpg', + }, + { + id: '56a3e7ce-01da-43fc-ab9f-a8a39fa980de', + name: 'Mcmillan Durham', + avatar: 'images/avatars/male-08.jpg', + }, + { + id: '4d24cf48-a322-4d53-89cb-9140dfd5c6ba', + name: 'Jangorine Hebert', + avatar: 'images/avatars/female-10.jpg', + }, + { + id: 'b2e97a96-2f15-4e3d-aff5-4ddf2af924d4', + name: 'Susanna Kline', + avatar: 'images/avatars/female-11.jpg', + }, + { + id: '4678ad07-e057-48a9-a5d1-3cf98e722eeb', + name: 'Suzette Singleton', + avatar: 'images/avatars/female-12.jpg', + }, +]; +export const tasks = [ + { + id: 'f65d517a-6f69-4c88-81f5-416f47405ce1', + type: 'section', + title: 'Company internal application v2.0.0', + notes: 'Magna consectetur culpa duis ad est tempor pariatur velit ullamco aute exercitation magna sunt commodo minim enim aliquip eiusmod ipsum adipisicing magna ipsum reprehenderit lorem magna voluptate magna aliqua culpa.\n\nSit nisi adipisicing pariatur enim enim sunt officia ad labore voluptate magna proident velit excepteur pariatur cillum sit excepteur elit veniam excepteur minim nisi cupidatat proident dolore irure veniam mollit.', + completed: false, + dueDate: '2017-10-18T13:03:37.943Z', + priority: 1, + tags: [ + '91658b8a-f382-4b0c-a53f-e9390351c2c5', + '51483dd3-cb98-4400-9128-4bd66b455807', + ], + assignedTo: null, + subTasks: [ + { + id: '2768a969-a316-449b-bf82-93cff4252cbf', + title: 'Minim irure fugiat ullamco irure', + completed: false, + }, + { + id: '6cc5ac8f-3a02-47e6-ad4b-0bd0222e2717', + title: 'Sint velit ex in adipisicing fugiat', + completed: false, + }, + ], + order: 0, + }, + { + id: '0fcece82-1691-4b98-a9b9-b63218f9deef', + type: 'task', + title: 'Create the landing/marketing page and host it on the beta channel', + notes: 'Et in lorem qui ipsum deserunt duis exercitation lorem elit qui qui ipsum tempor nulla velit aliquip enim consequat incididunt pariatur duis excepteur elit irure nulla ipsum dolor dolore est.\n\nAute deserunt nostrud id non ipsum do adipisicing laboris in minim officia magna elit minim mollit elit velit veniam lorem pariatur veniam sit excepteur irure commodo excepteur duis quis in.', + completed: false, + dueDate: null, + priority: 0, + tags: [], + assignedTo: 'e2c81627-a8a1-4bbc-9adc-ac4281e040d4', + subTasks: [], + order: 1, + }, + { + id: '2e6971cd-49d5-49f1-8cbd-fba5c71e6062', + type: 'task', + title: 'Move dependency system to Yarn for easier package management', + notes: 'Id fugiat et cupidatat magna nulla nulla eu cillum officia nostrud dolore in veniam ullamco nulla ex duis est enim nisi aute ipsum velit et laboris est pariatur est culpa.\n\nCulpa sunt ipsum esse quis excepteur enim culpa est voluptate reprehenderit consequat duis officia irure voluptate veniam dolore fugiat dolor est amet nostrud non velit irure do voluptate id sit.', + completed: false, + dueDate: '2019-05-24T03:55:38.969Z', + priority: 0, + tags: [ + 'c6058d0d-a4b0-4453-986a-9d249ec230b1', + '2b884143-419a-45ca-a7f6-48f99f4e7798', + '91658b8a-f382-4b0c-a53f-e9390351c2c5', + ], + assignedTo: '88a2a76c-0e6f-49da-b617-46d7c3b6e64d', + subTasks: [ + { + id: 'b9566b52-82cd-4d2a-b9b6-240c6b44e52b', + title: 'Nulla officia elit adipisicing', + completed: false, + }, + { + id: '76f4dc8d-4803-4d98-b461-367a1d3746a8', + title: 'Magna nisi ut aliquip aliquip amet deserunt', + completed: false, + }, + ], + order: 2, + }, + { + id: '974f93b8-336f-4eec-b011-9ddb412ee828', + type: 'task', + title: 'Fix permission issues that the 0.0.7-alpha.2 has introduced', + notes: 'Excepteur deserunt tempor do lorem elit id magna pariatur irure ullamco elit dolor consectetur ad officia fugiat incididunt do elit aute esse eu voluptate adipisicing incididunt ea dolor aliqua dolor.\n\nConsequat est quis deserunt voluptate ipsum incididunt laboris occaecat irure laborum voluptate non sit labore voluptate sunt id sint ut laboris aute cupidatat occaecat eiusmod non magna aliquip deserunt nisi.', + completed: true, + dueDate: null, + priority: 2, + tags: ['a0bf42ca-c3a5-47be-8341-b9c0bb8ef270'], + assignedTo: null, + subTasks: [ + { + id: '8e9644dc-0815-4258-8a08-4ce8d9912ec0', + title: 'Adipisicing aliquip voluptate veniam', + completed: false, + }, + { + id: 'fc0f2283-3802-4ebe-b164-774bc2b84549', + title: 'Magna amet adipisicing velit nisi est', + completed: false, + }, + { + id: '8a74b56f-14c0-4700-b737-8ccfa912f4b6', + title: 'Eiusmod dolore voluptate excepteur ipsum nostrud', + completed: false, + }, + { + id: '439ed5b7-156d-414a-ba20-ce779e3ec037', + title: 'Laborum adipisicing quis culpa amet', + completed: true, + }, + ], + order: 3, + }, + { + id: '5d877fc7-b881-4527-a6aa-d39d642feb23', + type: 'task', + title: 'Start Twitter promotions using the company Twitter account', + notes: 'Labore mollit in aliqua exercitation aliquip elit nisi nisi voluptate reprehenderit et dolor incididunt cupidatat ullamco nulla consequat voluptate adipisicing dolor qui magna sint aute do excepteur in aliqua consectetur.\n\nElit laborum non duis irure ad ullamco aliqua enim exercitation quis fugiat aute esse esse magna et ad cupidatat voluptate sint nulla nulla lorem et enim deserunt proident deserunt consectetur.', + completed: true, + dueDate: null, + priority: 1, + tags: ['51483dd3-cb98-4400-9128-4bd66b455807'], + assignedTo: '4678ad07-e057-48a9-a5d1-3cf98e722eeb', + subTasks: [ + { + id: 'b076c673-7d76-43b5-aaca-d0c496f397e5', + title: 'Esse dolore nostrud lorem consectetur', + completed: false, + }, + { + id: 'a01522ff-07fa-4fbd-a168-47802446b705', + title: 'Lorem velit voluptate laborum ad', + completed: false, + }, + ], + order: 4, + }, + { + id: '3d1c26c5-1e5e-4eb6-8006-ed6037ed9aca', + type: 'task', + title: 'Add more error pages - 401, 301, 303, 500 etc.', + notes: 'Sunt mollit irure dolor aliquip sit veniam amet ut sunt dolore cillum sint pariatur qui irure proident velit non excepteur quis ut et quis velit aliqua ea sunt cillum sit.\n\nReprehenderit est culpa ut incididunt sit dolore mollit in occaecat velit culpa consequat reprehenderit ex lorem cupidatat proident reprehenderit ad eu sunt sit ut sit culpa ea reprehenderit aliquip est.', + completed: false, + dueDate: '2018-09-29T19:30:45.325Z', + priority: 1, + tags: ['c6058d0d-a4b0-4453-986a-9d249ec230b1'], + assignedTo: '6617b0a3-0ccd-44ea-af78-c6633115d683', + subTasks: [], + order: 5, + }, + { + id: '11bd2b9a-85b4-41c9-832c-bd600dfa3a52', + type: 'task', + title: 'Clear the caches before the production build', + notes: 'Sint mollit consectetur voluptate fugiat sunt ipsum adipisicing labore exercitation eiusmod enim excepteur enim proident velit sint magna commodo dolor ex ipsum sit nisi deserunt labore eu irure amet ea.\n\nOccaecat ut velit et sint pariatur laboris voluptate duis aliqua aliqua exercitation et duis duis eu laboris excepteur occaecat quis esse enim ex dolore commodo fugiat excepteur adipisicing in fugiat.', + completed: true, + dueDate: '2017-10-12T12:03:55.559Z', + priority: 2, + tags: [], + assignedTo: '271e6a06-0d37-433d-bc8d-607b12bcbed9', + subTasks: [ + { + id: '9cd8eba8-7c41-4230-9d80-f71f7ed1cfe9', + title: 'Eu exercitation proident dolore velit', + completed: true, + }, + ], + order: 6, + }, + { + id: 'f55c023a-785e-4f0f-b5b7-47da75224deb', + type: 'task', + title: 'Examine the package loss rates that the 0.0.7-alpha.1 has introduced', + notes: 'In exercitation sunt ad anim commodo sunt do in sunt est officia amet ex ullamco do nisi consectetur lorem proident lorem adipisicing incididunt consequat fugiat voluptate sint est anim officia.\n\nVelit sint aliquip elit culpa amet eu mollit veniam esse deserunt ex occaecat quis lorem minim occaecat culpa esse veniam enim duis excepteur ipsum esse ut ut velit cillum adipisicing.', + completed: false, + dueDate: '2022-06-05T19:41:12.501Z', + priority: 2, + tags: [], + assignedTo: '7f5db993-ec36-412f-9db3-16d076a98807', + subTasks: [ + { + id: 'cdb08aa2-980d-48c6-b15c-7970775b7b5a', + title: 'Veniam magna minim duis', + completed: true, + }, + { + id: 'dc19e213-687e-4391-8b61-9aabed2fb288', + title: 'Eu dolore et adipisicing commodo adipisicing consequat', + completed: false, + }, + { + id: '7e365400-59b9-4ec9-b397-8bf40de56ec4', + title: 'Do culpa quis consequat cupidatat', + completed: true, + }, + { + id: '1a0f98b0-dfc4-4ac9-b8f5-ce322da2a849', + title: 'Est duis do sunt esse magna ex', + completed: true, + }, + ], + order: 7, + }, + { + id: 'c577a67d-357a-4b88-96e8-a0ee1fe9162e', + type: 'task', + title: 'Start Google ads using the company coupons', + notes: 'Ad adipisicing duis consequat magna sunt consequat aliqua eiusmod qui et nostrud voluptate sit enim reprehenderit anim exercitation ipsum ipsum anim ipsum laboris aliqua ex lorem aute officia voluptate culpa.\n\nNostrud anim ex pariatur ipsum et nostrud esse veniam ipsum ipsum irure velit ad quis irure tempor nulla amet aute id esse reprehenderit ea consequat consequat ea minim magna magna.', + completed: false, + dueDate: '2020-04-06T02:57:58.506Z', + priority: 1, + tags: [ + 'c6058d0d-a4b0-4453-986a-9d249ec230b1', + 'a0bf42ca-c3a5-47be-8341-b9c0bb8ef270', + ], + assignedTo: 'a21ec32e-54ba-480b-afdc-d1cbe18a96fd', + subTasks: [ + { + id: 'b1849778-a69c-46ad-8373-99aa6a655965', + title: 'Ipsum ipsum occaecat nulla', + completed: true, + }, + { + id: '8325f17a-2af0-4f64-b043-8ffdaaa62408', + title: 'Quis proident amet id non nulla', + completed: true, + }, + ], + order: 8, + }, + { + id: '1a680c29-7ece-4a80-9709-277ad4da8b4b', + type: 'section', + title: 'Developer API for the payment system', + notes: 'Magna laborum et amet magna fugiat officia deserunt in exercitation aliquip nulla magna velit ea labore quis deserunt ipsum occaecat id id consequat non eiusmod mollit est voluptate ea ex.\n\nReprehenderit mollit ut excepteur minim veniam fugiat enim id pariatur amet elit nostrud occaecat pariatur et esse aliquip irure quis officia reprehenderit voluptate voluptate est et voluptate sint esse dolor.', + completed: false, + dueDate: '2020-02-08T22:42:35.937Z', + priority: 2, + tags: [ + 'a0bf42ca-c3a5-47be-8341-b9c0bb8ef270', + '2b884143-419a-45ca-a7f6-48f99f4e7798', + ], + assignedTo: '3e353312-6a9b-46af-adda-5061b06e806b', + subTasks: [], + order: 9, + }, + { + id: 'c49c2216-8bdb-4df0-be25-d5ea1dbb5688', + type: 'task', + title: 'Re-think the current API restrictions to loosen them a bit', + notes: 'Adipisicing laboris ipsum fugiat et cupidatat aute esse ad labore et est cillum ipsum sunt duis do veniam minim officia deserunt in eiusmod eu duis dolore excepteur consectetur id elit.\n\nAnim excepteur occaecat laborum sunt in elit quis sit duis adipisicing laboris anim laborum et pariatur elit qui consectetur laborum reprehenderit occaecat nostrud pariatur aliqua elit nisi commodo eu excepteur.', + completed: false, + dueDate: '2019-08-10T06:18:17.785Z', + priority: 1, + tags: ['a0bf42ca-c3a5-47be-8341-b9c0bb8ef270'], + assignedTo: '368aab1e-ebce-43ba-8925-4cf13937867b', + subTasks: [ + { + id: '756ceee7-a9b2-45b6-9f22-5be974da7cf5', + title: 'Irure incididunt adipisicing consectetur enim', + completed: false, + }, + ], + order: 10, + }, + { + id: '3ef176fa-6cba-4536-9f43-540c686a4faa', + type: 'task', + title: 'Pre-flight checks causes random crashes on logging service', + notes: 'Culpa duis nostrud qui velit sint magna officia fugiat ipsum eiusmod enim laborum pariatur anim culpa elit ipsum lorem pariatur exercitation laborum do labore cillum exercitation nisi reprehenderit exercitation quis.\n\nMollit aute dolor non elit et incididunt eiusmod non in commodo occaecat id in excepteur aliqua ea anim pariatur sint elit voluptate dolor eu non laborum laboris voluptate qui duis.', + completed: false, + dueDate: '2024-08-23T14:33:06.227Z', + priority: 2, + tags: ['91658b8a-f382-4b0c-a53f-e9390351c2c5'], + assignedTo: '271e6a06-0d37-433d-bc8d-607b12bcbed9', + subTasks: [ + { + id: '35b06803-2019-4025-b642-841e44de7571', + title: 'Reprehenderit et eiusmod do consectetur ipsum', + completed: false, + }, + { + id: '7ec47bbc-e644-45ae-84e3-de36ee35a22b', + title: 'Officia lorem tempor occaecat fugiat elit elit', + completed: false, + }, + { + id: 'b4560302-7bed-412c-8e43-a5ce0bce5eed', + title: 'Incididunt commodo amet fugiat nulla et', + completed: false, + }, + { + id: '494bfcac-44ee-46db-add2-0e5dbc3952c4', + title: 'Enim ipsum fugiat ipsum aute quis', + completed: true, + }, + { + id: 'ffa45bc0-4466-4584-891a-0f75e39766c1', + title: 'Esse excepteur commodo ullamco', + completed: true, + }, + ], + order: 11, + }, + { + id: '7bc6b7b4-7ad8-4cbe-af36-7301642d35fb', + type: 'task', + title: 'Increase the timeout amount to allow more retries on client side', + notes: 'Ea proident dolor tempor dolore incididunt velit incididunt ullamco quis proident consectetur magna excepteur cillum officia ex do aliqua reprehenderit est esse officia labore dolore aute laboris eu commodo aute.\n\nOfficia quis id ipsum adipisicing ipsum eu exercitation cillum ex elit pariatur adipisicing ullamco ullamco nulla dolore magna aliqua reprehenderit eu laborum voluptate reprehenderit non eiusmod deserunt velit magna do.', + completed: true, + dueDate: '2017-08-16T12:56:48.039Z', + priority: 1, + tags: [ + '51483dd3-cb98-4400-9128-4bd66b455807', + 'd3ef4226-ef2c-43b0-a986-3e3e07f32799', + 'a0bf42ca-c3a5-47be-8341-b9c0bb8ef270', + ], + assignedTo: '4d24cf48-a322-4d53-89cb-9140dfd5c6ba', + subTasks: [ + { + id: 'a72f756b-e1db-4492-96b9-93785400e8bb', + title: 'Amet eiusmod consequat non culpa', + completed: false, + }, + { + id: '07fb282a-141a-4014-96d2-030894a6e211', + title: 'Nulla laboris veniam qui et nostrud enim', + completed: false, + }, + { + id: '40629855-8ba8-4590-9ebe-2e2ff3f20820', + title: 'Est est nulla cillum aliquip duis ipsum', + completed: true, + }, + { + id: '96e283b2-cd3e-4ab9-9770-07247691304b', + title: 'Non elit tempor commodo enim laboris', + completed: true, + }, + { + id: '95c6a48a-4e42-4909-8c25-0fafd62aeefa', + title: 'Proident est anim do laborum nostrud', + completed: false, + }, + ], + order: 12, + }, + { + id: '56c9ed66-a1d2-4803-a160-fba29b826cb4', + type: 'task', + title: 'Create the landing/marketing page and host it on the beta channel', + notes: 'Elit cillum incididunt enim cupidatat ex elit cillum aute dolor consectetur proident non minim eu est deserunt proident mollit ullamco laborum anim ea labore anim ex enim ullamco consectetur enim.\n\nEx magna consectetur esse enim consequat non aliqua nulla labore mollit sit quis ex fugiat commodo eu cupidatat irure incididunt consequat enim ut deserunt consequat elit consequat sint adipisicing sunt.', + completed: true, + dueDate: '2023-09-15T15:12:36.910Z', + priority: 0, + tags: ['2b884143-419a-45ca-a7f6-48f99f4e7798'], + assignedTo: '3a23baf7-2db8-4ef5-8d49-86d3e708dff5', + subTasks: [ + { + id: 'f1890ef6-89ed-47ca-a124-8305d7fe71fd', + title: 'Sit eu aliqua et et', + completed: true, + }, + { + id: '647f63b9-27b8-4d65-8e09-874ef5a48573', + title: 'Voluptate esse cillum commodo', + completed: true, + }, + { + id: '2934f015-1fd1-41c0-8b5a-d7adb5c50553', + title: 'Qui commodo fugiat eiusmod sint anim', + completed: true, + }, + { + id: 'f964fc8d-662c-4586-a39f-dab6674f2760', + title: 'Consequat nulla anim velit reprehenderit', + completed: false, + }, + { + id: 'ab3dd1a9-a9fb-4864-8630-da270cf71ee5', + title: 'Adipisicing officia ex laboris', + completed: true, + }, + ], + order: 13, + }, + { + id: '21c1b662-33c8-44d7-9530-91896afeeac7', + type: 'task', + title: 'Move dependency system to Yarn for easier package management', + notes: 'Duis culpa ut veniam voluptate consequat proident magna eiusmod id est magna culpa nulla enim culpa mollit velit lorem mollit ut minim dolore in tempor reprehenderit cillum occaecat proident ea.\n\nVeniam fugiat ea duis qui et eu eiusmod voluptate id cillum eiusmod eu reprehenderit minim reprehenderit nisi cillum nostrud duis eu magna minim sunt voluptate eu pariatur nulla ullamco elit.', + completed: true, + dueDate: '2020-08-08T16:32:24.768Z', + priority: 1, + tags: [], + assignedTo: null, + subTasks: [ + { + id: 'e5fece14-cc26-40df-9319-23568cf89662', + title: 'Tempor qui eiusmod et', + completed: false, + }, + { + id: '30e6117d-e2a2-4f97-a674-19a554a94829', + title: 'Tempor magna eu dolore aliquip', + completed: false, + }, + { + id: 'a5dd7270-1bc7-4b2b-abf0-9366eaca972d', + title: 'Lorem duis esse commodo', + completed: false, + }, + { + id: '40ffd839-046f-4272-9232-5391d62477f7', + title: 'Minim aute eu ut id', + completed: false, + }, + ], + order: 14, + }, + { + id: '5fa52c90-82be-41ae-96ec-5fc67cf054a4', + type: 'task', + title: 'Fix permission issues that the 0.0.7-alpha.2 has introduced', + notes: 'Mollit nostrud ea irure ex ipsum in cupidatat irure sit officia reprehenderit adipisicing et occaecat cupidatat exercitation mollit esse in excepteur qui elit exercitation velit fugiat exercitation est officia excepteur.\n\nQuis esse voluptate laborum non veniam duis est fugiat tempor culpa minim velit minim ut duis qui officia consectetur ex nostrud ut elit elit nulla in consectetur voluptate aliqua aliqua.', + completed: false, + dueDate: '2019-10-13T08:25:17.064Z', + priority: 0, + tags: ['2b884143-419a-45ca-a7f6-48f99f4e7798'], + assignedTo: 'b2e97a96-2f15-4e3d-aff5-4ddf2af924d4', + subTasks: [ + { + id: '2ef107fb-3c21-4801-861f-abaf4fd6def0', + title: 'Voluptate qui excepteur id in', + completed: true, + }, + { + id: '0afb4ebf-fcc7-47dc-8351-a88cb47c39ee', + title: 'Laborum ipsum aute nisi anim', + completed: false, + }, + { + id: '2f22bff2-72be-4ff5-b037-c3bf0f1d5637', + title: 'Amet duis velit sunt non', + completed: false, + }, + ], + order: 15, + }, + { + id: 'b6d8909f-f36d-4885-8848-46b8230d4476', + type: 'task', + title: 'Start Twitter promotions using the company Twitter account', + notes: 'Laboris ea nisi commodo nulla cillum consequat consectetur nisi velit adipisicing minim nulla culpa amet quis sit duis id id aliqua aute exercitation non reprehenderit aliquip enim eiusmod eu irure.\n\nNon irure consectetur sunt cillum do adipisicing excepteur labore proident ut officia dolor fugiat velit sint consectetur cillum qui amet enim anim mollit laboris consectetur non do laboris lorem aliqua.', + completed: true, + dueDate: '2020-02-03T05:39:30.880Z', + priority: 1, + tags: ['2b884143-419a-45ca-a7f6-48f99f4e7798'], + assignedTo: '65e15136-5168-4655-8bbc-e3ad8a94bf67', + subTasks: [], + order: 16, + }, + { + id: '9496235d-4d0c-430b-817e-1cba96404f95', + type: 'task', + title: 'Add more error pages - 401, 301, 303, 500 etc.', + notes: 'Ullamco eiusmod do pariatur pariatur consectetur commodo proident ex voluptate ullamco culpa commodo deserunt pariatur incididunt nisi magna dolor est minim eu ex voluptate deserunt labore id magna excepteur et.\n\nReprehenderit dolore pariatur exercitation ad non fugiat quis proident fugiat incididunt ea magna pariatur et exercitation tempor cillum eu consequat adipisicing est laborum sit cillum ea fugiat mollit cupidatat est.', + completed: true, + dueDate: '2020-03-09T19:42:06.383Z', + priority: 1, + tags: [], + assignedTo: '7f5db993-ec36-412f-9db3-16d076a98807', + subTasks: [ + { + id: '9e710568-306f-47f9-b397-5634dc7a1a52', + title: 'Lorem excepteur non anim non exercitation fugiat', + completed: true, + }, + { + id: 'bd10d3d5-22d1-467d-aa6b-431d23203f51', + title: 'Nulla non in occaecat nulla', + completed: false, + }, + { + id: '0a768b47-7248-4000-a201-e51f86401317', + title: 'Dolor qui deserunt duis enim do veniam', + completed: true, + }, + ], + order: 17, + }, + { + id: '7fde17e6-4ac1-47dd-a363-2f4f14dcf76a', + type: 'task', + title: 'Clear the caches before the production build', + notes: 'Qui quis nulla excepteur voluptate elit culpa occaecat id ex do adipisicing est mollit id anim nisi irure amet officia ut sint aliquip dolore labore cupidatat magna laborum esse ea.\n\nEnim magna duis sit incididunt amet anim et nostrud laborum eiusmod et ea fugiat aliquip velit sit fugiat consectetur ipsum anim do enim excepteur cupidatat consequat sunt irure tempor ut.', + completed: true, + dueDate: '2022-08-24T03:03:09.899Z', + priority: 1, + tags: [ + '2b884143-419a-45ca-a7f6-48f99f4e7798', + '91658b8a-f382-4b0c-a53f-e9390351c2c5', + 'c6058d0d-a4b0-4453-986a-9d249ec230b1', + 'a0bf42ca-c3a5-47be-8341-b9c0bb8ef270', + ], + assignedTo: '88a2a76c-0e6f-49da-b617-46d7c3b6e64d', + subTasks: [ + { + id: 'f82708c3-2b58-4ac0-b58c-164c0804c631', + title: 'Mollit laborum tempor lorem cupidatat dolore nostrud', + completed: true, + }, + { + id: '0017121e-79fc-403c-bb1c-84dc28f79e06', + title: 'Nisi sint sint et et ad', + completed: true, + }, + { + id: '9073242a-5be6-487a-9e50-ea298700af79', + title: 'Duis nulla ad magna', + completed: false, + }, + ], + order: 18, + }, + { + id: '90a3ed58-e13b-40cf-9219-f933bf9c9b8f', + type: 'task', + title: 'Examine the package loss rates that the 0.0.7-alpha.1 has introduced', + notes: 'Consequat consectetur commodo deserunt sunt aliquip deserunt ex tempor esse nostrud sit dolore anim nostrud nulla dolore veniam minim laboris non dolor veniam lorem veniam deserunt laborum aute amet irure.\n\nEiusmod officia veniam reprehenderit ea aliquip velit anim aute minim aute nisi tempor qui sunt deserunt voluptate velit elit ut adipisicing ipsum et excepteur ipsum eu ullamco nisi esse dolor.', + completed: false, + dueDate: '2023-10-04T15:48:16.507Z', + priority: 1, + tags: ['d3ef4226-ef2c-43b0-a986-3e3e07f32799'], + assignedTo: null, + subTasks: [ + { + id: 'eaab24ed-cf9e-4ee7-b7ff-acd8f62f617a', + title: 'Eiusmod nulla enim laborum deserunt in', + completed: false, + }, + { + id: '700d067c-c5be-4532-95e3-ba575effae7c', + title: 'Sunt sint ea est commodo id', + completed: false, + }, + ], + order: 19, + }, + { + id: '81ac908c-35a2-4705-8d75-539863c35c09', + type: 'task', + title: 'Start Google ads using the company coupons', + notes: 'Sit occaecat sint nulla in esse dolor occaecat in ea sit irure magna magna veniam fugiat consequat exercitation ipsum ex officia velit consectetur consequat voluptate lorem eu proident lorem incididunt.\n\nExcepteur exercitation et qui labore nisi eu voluptate ipsum deserunt deserunt eu est minim dolor ad proident nulla reprehenderit culpa minim voluptate dolor nostrud dolor anim labore aliqua officia nostrud.', + completed: true, + dueDate: '2024-02-01T10:02:52.745Z', + priority: 1, + tags: ['a0bf42ca-c3a5-47be-8341-b9c0bb8ef270'], + assignedTo: '368aab1e-ebce-43ba-8925-4cf13937867b', + subTasks: [ + { + id: '651a87c6-4376-42c4-9dfd-fad7525e7eb3', + title: 'Aliqua est excepteur excepteur deserunt id', + completed: true, + }, + ], + order: 20, + }, + { + id: '153376ed-691f-4dfd-ae99-e204a49edc44', + type: 'task', + title: 'Re-think the current API restrictions to loosen them a bit', + notes: 'Duis sint velit incididunt exercitation eiusmod nisi sunt ex est fugiat ad cupidatat sunt nisi elit do duis amet voluptate ipsum aliquip lorem aliqua sint esse in magna irure officia.\n\nNon eu ex elit ut est voluptate tempor amet ut officia in duis deserunt cillum labore do culpa id dolore magna anim consectetur qui consectetur fugiat labore mollit magna irure.', + completed: true, + dueDate: '2021-02-22T17:42:00.257Z', + priority: 2, + tags: [], + assignedTo: '65f1c421-83c5-4cdf-99da-d97794328679', + subTasks: [], + order: 21, + }, + { + id: '1ebde495-1bcd-4e8f-b6f6-cf63b521ad06', + type: 'section', + title: 'Marketing and promotions for the mobile app', + notes: 'Aute commodo reprehenderit cupidatat duis nulla mollit sint cupidatat elit adipisicing fugiat sunt cupidatat amet proident fugiat aute adipisicing et non minim occaecat ea esse consectetur aute culpa exercitation incididunt.\n\nEnim et lorem anim dolor excepteur qui tempor cupidatat do proident adipisicing esse incididunt mollit quis irure amet ad officia culpa minim cillum veniam voluptate lorem exercitation sunt cillum dolor.', + completed: false, + dueDate: '2018-08-04T19:32:53.652Z', + priority: 1, + tags: [], + assignedTo: 'e62ab50e-90d3-4ed7-a911-093bb44d0c50', + subTasks: [ + { + id: 'c5a8b915-0b0f-4dd3-a1a3-e538fa191747', + title: 'Adipisicing do minim voluptate', + completed: true, + }, + { + id: '52b50615-0d80-42b6-97cb-1b71eaec1632', + title: 'Et eiusmod est adipisicing officia', + completed: true, + }, + ], + order: 22, + }, + { + id: '4e7ce72f-863a-451f-9160-cbd4fbbc4c3d', + type: 'task', + title: 'Pre-flight checks causes random crashes on logging service', + notes: 'Exercitation sit eiusmod enim officia exercitation eiusmod sunt eiusmod excepteur ad commodo eiusmod qui proident quis aliquip excepteur sit cillum occaecat non dolore sit in labore ut duis esse duis.\n\nConsequat sunt voluptate consectetur dolor laborum enim nostrud deserunt incididunt sint veniam laboris sunt amet velit anim duis aliqua sunt aliqua aute qui nisi mollit qui irure ullamco aliquip laborum.', + completed: true, + dueDate: '2020-09-29T02:25:14.111Z', + priority: 1, + tags: [], + assignedTo: 'ef44b39b-3272-45f5-a15e-264c3b2d118e', + subTasks: [ + { + id: '654c9b65-6f94-4ae7-bf11-27f979cc670e', + title: 'Esse exercitation cillum ex', + completed: false, + }, + { + id: '3c49aba9-1e83-471f-b8b8-21cc7d20292e', + title: 'Duis sunt commodo fugiat irure minim', + completed: false, + }, + { + id: '4fcb2e0b-677c-4915-978d-70e82b16745a', + title: 'Anim in qui ut', + completed: false, + }, + { + id: 'dd864dea-61d2-4fb0-b433-286993b6ad08', + title: 'Reprehenderit irure exercitation occaecat', + completed: true, + }, + ], + order: 23, + }, + { + id: '0795a74f-7a84-4edf-8d66-296cdef70003', + type: 'task', + title: 'Increase the timeout amount to allow more retries on client side', + notes: 'Minim commodo cillum do id qui irure aliqua laboris excepteur laboris magna enim est lorem consectetur tempor laboris proident proident eu irure dolor eiusmod in officia lorem quis laborum ullamco.\n\nQui excepteur ex sit esse dolore deserunt ullamco occaecat laboris fugiat cupidatat excepteur laboris amet dolore enim velit ipsum velit sint cupidatat consectetur cupidatat deserunt sit eu do ullamco quis.', + completed: true, + dueDate: '2019-03-09T02:34:29.592Z', + priority: 2, + tags: [ + 'c6058d0d-a4b0-4453-986a-9d249ec230b1', + 'd3ef4226-ef2c-43b0-a986-3e3e07f32799', + ], + assignedTo: '6617b0a3-0ccd-44ea-af78-c6633115d683', + subTasks: [ + { + id: '56f3dccb-a72b-485c-94e7-fe68477023e2', + title: 'Velit velit voluptate in occaecat nostrud', + completed: true, + }, + { + id: '70cb77a9-82fa-407b-a63e-55aedc241495', + title: 'Minim anim velit eiusmod qui', + completed: true, + }, + { + id: '08a31dbc-6be4-469b-9ff4-0ed5342082bd', + title: 'Laboris commodo laborum irure', + completed: false, + }, + { + id: '34d6c603-6f5a-4bc4-9f94-12bfd940c3c7', + title: 'Mollit mollit nostrud mollit id velit ullamco', + completed: true, + }, + ], + order: 24, + }, + { + id: '05532574-c102-4228-89a8-55fff32ec6fc', + type: 'task', + title: 'Create the landing/marketing page and host it on the beta channel', + notes: 'Reprehenderit anim consectetur anim dolor magna consequat excepteur tempor enim duis magna proident ullamco aute voluptate elit laborum mollit labore id ex lorem est mollit do qui ex labore nulla.\n\nUt proident elit proident adipisicing elit fugiat ex ullamco dolore excepteur excepteur labore laborum sunt ipsum proident magna ex voluptate laborum voluptate sint proident eu reprehenderit non excepteur quis eiusmod.', + completed: true, + dueDate: '2023-12-08T23:20:50.910Z', + priority: 2, + tags: ['a0bf42ca-c3a5-47be-8341-b9c0bb8ef270'], + assignedTo: null, + subTasks: [], + order: 25, + }, + { + id: 'b3917466-aa51-4293-9d5b-120b0ce6635c', + type: 'task', + title: 'Move dependency system to Yarn for easier package management', + notes: 'Ipsum officia mollit qui laboris sunt amet aliquip cupidatat minim non elit commodo eiusmod labore mollit pariatur aute reprehenderit ullamco occaecat enim pariatur aute amet occaecat incididunt irure ad ut.\n\nIncididunt cupidatat pariatur magna sint sit culpa ad cupidatat cillum exercitation consequat minim pariatur consectetur aliqua non adipisicing magna ad nulla ea do est nostrud eu aute id occaecat ut.', + completed: false, + dueDate: '2018-01-14T09:58:38.444Z', + priority: 1, + tags: [], + assignedTo: '56a3e7ce-01da-43fc-ab9f-a8a39fa980de', + subTasks: [ + { + id: '3a4c4013-27f1-4164-8a64-e7bb4f1a63a9', + title: 'Adipisicing excepteur mollit non sunt amet laboris', + completed: false, + }, + { + id: '103bf29e-06a1-4d30-89b9-b67aa442d605', + title: 'Consectetur voluptate anim labore aliqua elit', + completed: false, + }, + { + id: 'b77729f1-9ed1-4d9e-95d0-347f4cd0943c', + title: 'Laboris occaecat aliquip esse magna nulla', + completed: true, + }, + { + id: '695aace7-8679-4b35-96c7-cf23737cd9f1', + title: 'Exercitation eu aliquip cillum ipsum', + completed: false, + }, + { + id: 'ffd45f31-7f0a-4c6a-b62c-18148f6841db', + title: 'Minim aute ad et esse officia nostrud', + completed: true, + }, + ], + order: 26, + }, + { + id: '2f2fb472-24d4-4a00-aa80-d513fa6c059c', + type: 'task', + title: 'Fix permission issues that the 0.0.7-alpha.2 has introduced', + notes: 'Dolor cupidatat do qui in tempor dolor magna magna ut dolor est aute veniam consectetur enim sunt sunt duis magna magna aliquip id reprehenderit dolor in veniam ullamco incididunt occaecat.\n\nId duis pariatur anim cillum est sint non veniam voluptate deserunt anim nostrud duis voluptate occaecat elit ut veniam voluptate do qui est ad velit irure sint lorem ullamco aliqua.', + completed: true, + dueDate: '2020-06-08T00:23:24.051Z', + priority: 1, + tags: ['91658b8a-f382-4b0c-a53f-e9390351c2c5'], + assignedTo: '65f1c421-83c5-4cdf-99da-d97794328679', + subTasks: [ + { + id: '4028671b-ef75-4b76-a03f-9f2bddadc618', + title: 'Commodo excepteur proident ipsum reprehenderit', + completed: true, + }, + { + id: 'b122168f-8327-408f-8b9c-498dd6ba6c81', + title: 'Mollit ullamco eiusmod exercitation deserunt', + completed: false, + }, + { + id: 'f233d812-be56-4d8a-ab14-a083f7d7cd70', + title: 'Mollit nostrud ea deserunt mollit aliquip', + completed: false, + }, + { + id: '0833be70-82b2-46cb-ad84-f11120ea634a', + title: 'Labore occaecat proident ullamco', + completed: false, + }, + ], + order: 27, + }, + { + id: '2fffd148-7644-466d-8737-7dde88c54154', + type: 'task', + title: 'Start Twitter promotions using the company Twitter account', + notes: 'Velit commodo pariatur ullamco elit sunt dolor quis irure amet tempor laboris labore tempor nisi consectetur ea proident dolore culpa nostrud esse amet commodo do esse laboris laboris in magna.\n\nAute officia labore minim laborum irure cupidatat occaecat laborum ex labore ipsum aliqua cillum do exercitation esse et veniam excepteur mollit incididunt ut qui irure culpa qui deserunt nostrud tempor.', + completed: false, + dueDate: '2024-01-27T11:17:52.198Z', + priority: 1, + tags: ['d3ef4226-ef2c-43b0-a986-3e3e07f32799'], + assignedTo: 'b2e97a96-2f15-4e3d-aff5-4ddf2af924d4', + subTasks: [ + { + id: 'd2ffe439-2f80-4dce-84a7-d4ac5e17bbf3', + title: 'Occaecat anim sunt dolor proident', + completed: false, + }, + ], + order: 28, + }, + { + id: '24a1034e-b4d6-4a86-a1ea-90516e87e810', + type: 'task', + title: 'Add more error pages - 401, 301, 303, 500 etc.', + notes: 'Exercitation eu in officia lorem commodo pariatur pariatur nisi consectetur qui elit in aliquip et ullamco duis nostrud aute laborum laborum est dolor non qui amet deserunt ex et aliquip.\n\nProident consectetur eu amet minim labore anim ad non aute duis eiusmod sit ad elit magna do aliquip aliqua laborum dolor laboris ea irure duis mollit fugiat tempor eu est.', + completed: false, + dueDate: '2024-06-24T04:38:28.087Z', + priority: 1, + tags: ['51483dd3-cb98-4400-9128-4bd66b455807'], + assignedTo: '7f5db993-ec36-412f-9db3-16d076a98807', + subTasks: [ + { + id: '75f55d75-c835-4a6c-a2ae-7a42ae3a7c9d', + title: 'Et laboris quis lorem est laboris', + completed: true, + }, + { + id: 'c7c91a52-b060-45af-b1b1-a4cff26bf11e', + title: 'Reprehenderit elit dolore exercitation pariatur', + completed: true, + }, + ], + order: 29, + }, +]; diff --git a/src/app/mock-api/common/auth/api.ts b/src/app/mock-api/common/auth/api.ts new file mode 100644 index 0000000..7b8d841 --- /dev/null +++ b/src/app/mock-api/common/auth/api.ts @@ -0,0 +1,227 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { user as userData } from 'app/mock-api/common/user/data'; +import Base64 from 'crypto-js/enc-base64'; +import Utf8 from 'crypto-js/enc-utf8'; +import HmacSHA256 from 'crypto-js/hmac-sha256'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class AuthMockApi { + private readonly _secret: any; + private _user: any = userData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Set the mock-api + this._secret = + 'YOUR_VERY_CONFIDENTIAL_SECRET_FOR_SIGNING_JWT_TOKENS!!!'; + + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Forgot password - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/auth/forgot-password', 1000) + .reply(() => [200, true]); + + // ----------------------------------------------------------------------------------------------------- + // @ Reset password - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/auth/reset-password', 1000) + .reply(() => [200, true]); + + // ----------------------------------------------------------------------------------------------------- + // @ Sign in - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/auth/sign-in', 1500) + .reply(({ request }) => { + // Sign in successful + if ( + request.body.email === 'username@angor.io' && + request.body.password === 'admin' + ) { + return [ + 200, + { + user: cloneDeep(this._user), + accessToken: this._generateJWTToken(), + tokenType: 'bearer', + }, + ]; + } + + // Invalid credentials + return [404, false]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Sign in using the access token - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/auth/sign-in-with-token') + .reply(({ request }) => { + // Get the access token + const accessToken = request.body.accessToken; + + // Verify the token + if (this._verifyJWTToken(accessToken)) { + return [ + 200, + { + user: cloneDeep(this._user), + accessToken: this._generateJWTToken(), + tokenType: 'bearer', + }, + ]; + } + + // Invalid token + return [ + 401, + { + error: 'Invalid token', + }, + ]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Sign up - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onPost('api/auth/sign-up', 1500).reply(() => + // Simply return true + [200, true] + ); + + // ----------------------------------------------------------------------------------------------------- + // @ Unlock session - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/auth/unlock-session', 1500) + .reply(({ request }) => { + // Sign in successful + if ( + request.body.email === 'username@angor.io' && + request.body.password === 'admin' + ) { + return [ + 200, + { + user: cloneDeep(this._user), + accessToken: this._generateJWTToken(), + tokenType: 'bearer', + }, + ]; + } + + // Invalid credentials + return [404, false]; + }); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Private methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Return base64 encoded version of the given string + * + * @param source + * @private + */ + private _base64url(source: any): string { + // Encode in classical base64 + let encodedSource = Base64.stringify(source); + + // Remove padding equal characters + encodedSource = encodedSource.replace(/=+$/, ''); + + // Replace characters according to base64url specifications + encodedSource = encodedSource.replace(/\+/g, '-'); + encodedSource = encodedSource.replace(/\//g, '_'); + + // Return the base64 encoded string + return encodedSource; + } + + /** + * Generates a JWT token using CryptoJS library. + * + * This generator is for mocking purposes only and it is NOT + * safe to use it in production frontend applications! + * + * @private + */ + private _generateJWTToken(): string { + // Define token header + const header = { + alg: 'HS256', + typ: 'JWT', + }; + + // Calculate the issued at and expiration dates + const date = new Date(); + const iat = Math.floor(date.getTime() / 1000); + const exp = Math.floor(date.setDate(date.getDate() + 7) / 1000); + + // Define token payload + const payload = { + iat: iat, + iss: 'Angor', + exp: exp, + }; + + // Stringify and encode the header + const stringifiedHeader = Utf8.parse(JSON.stringify(header)); + const encodedHeader = this._base64url(stringifiedHeader); + + // Stringify and encode the payload + const stringifiedPayload = Utf8.parse(JSON.stringify(payload)); + const encodedPayload = this._base64url(stringifiedPayload); + + // Sign the encoded header and mock-api + let signature: any = encodedHeader + '.' + encodedPayload; + signature = HmacSHA256(signature, this._secret); + signature = this._base64url(signature); + + // Build and return the token + return encodedHeader + '.' + encodedPayload + '.' + signature; + } + + /** + * Verify the given token + * + * @param token + * @private + */ + private _verifyJWTToken(token: string): boolean { + // Split the token into parts + const parts = token.split('.'); + const header = parts[0]; + const payload = parts[1]; + const signature = parts[2]; + + // Re-sign and encode the header and payload using the secret + const signatureCheck = this._base64url( + HmacSHA256(header + '.' + payload, this._secret) + ); + + // Verify that the resulting signature is valid + return signature === signatureCheck; + } +} diff --git a/src/app/mock-api/common/navigation/api.ts b/src/app/mock-api/common/navigation/api.ts new file mode 100644 index 0000000..050398c --- /dev/null +++ b/src/app/mock-api/common/navigation/api.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@angular/core'; +import { AngorNavigationItem } from '@angor/components/navigation'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { + compactNavigation, + defaultNavigation, + futuristicNavigation, + horizontalNavigation, +} from 'app/mock-api/common/navigation/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class NavigationMockApi { + private readonly _compactNavigation: AngorNavigationItem[] = + compactNavigation; + private readonly _defaultNavigation: AngorNavigationItem[] = + defaultNavigation; + private readonly _futuristicNavigation: AngorNavigationItem[] = + futuristicNavigation; + private readonly _horizontalNavigation: AngorNavigationItem[] = + horizontalNavigation; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Navigation - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/common/navigation').reply(() => { + // Fill compact navigation children using the default navigation + this._compactNavigation.forEach((compactNavItem) => { + this._defaultNavigation.forEach((defaultNavItem) => { + if (defaultNavItem.id === compactNavItem.id) { + compactNavItem.children = cloneDeep( + defaultNavItem.children + ); + } + }); + }); + + // Fill futuristic navigation children using the default navigation + this._futuristicNavigation.forEach((futuristicNavItem) => { + this._defaultNavigation.forEach((defaultNavItem) => { + if (defaultNavItem.id === futuristicNavItem.id) { + futuristicNavItem.children = cloneDeep( + defaultNavItem.children + ); + } + }); + }); + + // Fill horizontal navigation children using the default navigation + this._horizontalNavigation.forEach((horizontalNavItem) => { + this._defaultNavigation.forEach((defaultNavItem) => { + if (defaultNavItem.id === horizontalNavItem.id) { + horizontalNavItem.children = cloneDeep( + defaultNavItem.children + ); + } + }); + }); + + // Return the response + return [ + 200, + { + compact: cloneDeep(this._compactNavigation), + default: cloneDeep(this._defaultNavigation), + futuristic: cloneDeep(this._futuristicNavigation), + horizontal: cloneDeep(this._horizontalNavigation), + }, + ]; + }); + } +} diff --git a/src/app/mock-api/common/navigation/data.ts b/src/app/mock-api/common/navigation/data.ts new file mode 100644 index 0000000..0f110ec --- /dev/null +++ b/src/app/mock-api/common/navigation/data.ts @@ -0,0 +1,154 @@ +/* eslint-disable */ +import { AngorNavigationItem } from '@angor/components/navigation'; + +export const defaultNavigation: AngorNavigationItem[] = [ + { + id : 'home', + title: 'Home', + type : 'basic', + icon : 'heroicons_outline:home', + link : '/home' + }, + { + id : 'explore', + title: 'Explore', + type : 'basic', + icon : 'heroicons_outline:chart-pie', + link : '/explore' + }, + { + id : 'settings', + title: 'Settings', + type : 'basic', + icon : 'heroicons_outline:cog', + link : '/settings' + }, + { + id : 'profile', + title: 'Profile', + type : 'basic', + icon : 'heroicons_outline:user', + link : '/profile' + }, + { + id : 'chat', + title: 'Chat', + type : 'basic', + icon : 'heroicons_outline:chat-bubble-left-right', + link : '/chat' + } +]; + +export const compactNavigation: AngorNavigationItem[] = [ + { + id : 'home', + title: 'Home', + type : 'basic', + icon : 'heroicons_outline:home', + link : '/home' + }, + { + id : 'explore', + title: 'Explore', + type : 'basic', + icon : 'heroicons_outline:chart-pie', + link : '/explore' + }, + { + id : 'settings', + title: 'Settings', + type : 'basic', + icon : 'heroicons_outline:cog', + link : '/settings' + }, + { + id : 'profile', + title: 'Profile', + type : 'basic', + icon : 'heroicons_outline:user', + link : '/profile' + }, + { + id : 'chat', + title: 'Chat', + type : 'basic', + icon : 'heroicons_outline:chat-bubble-left-right', + link : '/chat' + } +]; + +export const futuristicNavigation: AngorNavigationItem[] = [ + { + id : 'home', + title: 'Home', + type : 'basic', + icon : 'heroicons_outline:home', + link : '/home' + }, + { + id : 'explore', + title: 'Explore', + type : 'basic', + icon : 'heroicons_outline:chart-pie', + link : '/explore' + }, + { + id : 'settings', + title: 'Settings', + type : 'basic', + icon : 'heroicons_outline:cog', + link : '/settings' + }, + { + id : 'profile', + title: 'Profile', + type : 'basic', + icon : 'heroicons_outline:user', + link : '/profile' + }, + { + id : 'chat', + title: 'Chat', + type : 'basic', + icon : 'heroicons_outline:chat-bubble-left-right', + link : '/chat' + } +]; + +export const horizontalNavigation: AngorNavigationItem[] = [ + { + id : 'home', + title: 'Home', + type : 'basic', + icon : 'heroicons_outline:home', + link : '/home' + }, + { + id : 'explore', + title: 'Explore', + type : 'basic', + icon : 'heroicons_outline:chart-pie', + link : '/explore' + }, + { + id : 'settings', + title: 'Settings', + type : 'basic', + icon : 'heroicons_outline:cog', + link : '/settings' + }, + { + id : 'profile', + title: 'Profile', + type : 'basic', + icon : 'heroicons_outline:user', + link : '/profile' + }, + { + id : 'chat', + title: 'Chat', + type : 'basic', + icon : 'heroicons_outline:chat-bubble-left-right', + link : '/chat' + } +]; diff --git a/src/app/mock-api/common/notifications/api.ts b/src/app/mock-api/common/notifications/api.ts new file mode 100644 index 0000000..5835089 --- /dev/null +++ b/src/app/mock-api/common/notifications/api.ts @@ -0,0 +1,161 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService, AngorMockApiUtils } from '@angor/lib/mock-api'; +import { notifications as notificationsData } from 'app/mock-api/common/notifications/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class NotificationsMockApi { + private _notifications: any = notificationsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Notifications - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/common/notifications') + .reply(() => [200, cloneDeep(this._notifications)]); + + // ----------------------------------------------------------------------------------------------------- + // @ Notifications - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/common/notifications') + .reply(({ request }) => { + // Get the notification + const newNotification = cloneDeep(request.body.notification); + + // Generate a new GUID + newNotification.id = AngorMockApiUtils.guid(); + + // Unshift the new notification + this._notifications.unshift(newNotification); + + // Return the response + return [200, newNotification]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notifications - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/common/notifications') + .reply(({ request }) => { + // Get the id and notification + const id = request.body.id; + const notification = cloneDeep(request.body.notification); + + // Prepare the updated notification + let updatedNotification = null; + + // Find the notification and update it + this._notifications.forEach( + (item: any, index: number, notifications: any[]) => { + if (item.id === id) { + // Update the notification + notifications[index] = assign( + {}, + notifications[index], + notification + ); + + // Store the updated notification + updatedNotification = notifications[index]; + } + } + ); + + // Return the response + return [200, updatedNotification]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Notifications - DELETE + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onDelete('api/common/notifications') + .reply(({ request }) => { + // Get the id + const id = request.params.get('id'); + + // Prepare the deleted notification + let deletedNotification = null; + + // Find the notification + const index = this._notifications.findIndex( + (item: any) => item.id === id + ); + + // Store the deleted notification + deletedNotification = cloneDeep(this._notifications[index]); + + // Delete the notification + this._notifications.splice(index, 1); + + // Return the response + return [200, deletedNotification]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Mark all as read - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/common/notifications/mark-all-as-read') + .reply(() => { + // Go through all notifications + this._notifications.forEach( + (item: any, index: number, notifications: any[]) => { + // Mark it as read + notifications[index].read = true; + notifications[index].seen = true; + } + ); + + // Return the response + return [200, true]; + }); + + // ----------------------------------------------------------------------------------------------------- + // @ Toggle read status - POST + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/common/notifications/toggle-read-status') + .reply(({ request }) => { + // Get the notification + const notification = cloneDeep(request.body.notification); + + // Prepare the updated notification + let updatedNotification = null; + + // Find the notification and update it + this._notifications.forEach( + (item: any, index: number, notifications: any[]) => { + if (item.id === notification.id) { + // Update the notification + notifications[index].read = notification.read; + + // Store the updated notification + updatedNotification = notifications[index]; + } + } + ); + + // Return the response + return [200, updatedNotification]; + }); + } +} diff --git a/src/app/mock-api/common/notifications/data.ts b/src/app/mock-api/common/notifications/data.ts new file mode 100644 index 0000000..6af91b2 --- /dev/null +++ b/src/app/mock-api/common/notifications/data.ts @@ -0,0 +1,96 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Retrieve the current date and time */ +const currentMoment = DateTime.now(); + +export const notifications = [ + { + id: '493190c9-5b61-4912-afe5-78c21f1044d7', + icon: 'heroicons_mini:star', + title: 'Daily Challenges', + description: 'Your submission has been approved', + time: currentMoment.minus({ minute: 25 }).toISO(), // 25 minutes ago + read: false, + }, + { + id: '6e3e97e5-effc-4fb7-b730-52a151f0b641', + image: 'images/avatars/male-04.jpg', + description: + 'Leo Gill has added you to the Top Secret Project group and assigned you as the Project Manager', + time: currentMoment.minus({ minute: 50 }).toISO(), // 50 minutes ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, + { + id: 'b91ccb58-b06c-413b-b389-87010e03a120', + icon: 'heroicons_mini:envelope', + title: 'Mailbox', + description: 'You have 15 unread emails across 3 mailboxes', + time: currentMoment.minus({ hour: 3 }).toISO(), // 3 hours ago + read: false, + link: '/dashboards/project', + useRouter: true, + }, + { + id: '541416c9-84a7-408a-8d74-27a43c38d797', + icon: 'heroicons_mini:arrow-path', + title: 'Cron Jobs', + description: 'Your Docker container is ready for publishing', + time: currentMoment.minus({ hour: 5 }).toISO(), // 5 hours ago + read: false, + link: '/dashboards/project', + useRouter: true, + }, + { + id: 'ef7b95a7-8e8b-4616-9619-130d9533add9', + image: 'images/avatars/male-06.jpg', + description: + 'Roger Murray has accepted your friend request', + time: currentMoment.minus({ hour: 7 }).toISO(), // 7 hours ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, + { + id: 'eb8aa470-635e-461d-88e1-23d9ea2a5665', + image: 'images/avatars/female-04.jpg', + description: 'Sophie Stone sent you a direct message', + time: currentMoment.minus({ hour: 9 }).toISO(), // 9 hours ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, + { + id: 'b85c2338-cc98-4140-bbf8-c226ce4e395e', + icon: 'heroicons_mini:envelope', + title: 'Mailbox', + description: 'You have 3 new unread emails', + time: currentMoment.minus({ day: 1 }).toISO(), // 1 day ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, + { + id: '8f8e1bf9-4661-4939-9e43-390957b60f42', + icon: 'heroicons_mini:star', + title: 'Daily Challenges', + description: + 'Your submission has been accepted, and you can now sign up for the final assignment, which will be available in 2 days', + time: currentMoment.minus({ day: 3 }).toISO(), // 3 days ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, + { + id: '30af917b-7a6a-45d1-822f-9e7ad7f8bf69', + icon: 'heroicons_mini:arrow-path', + title: 'Cron Jobs', + description: 'Your Vagrant container is ready for download', + time: currentMoment.minus({ day: 4 }).toISO(), // 4 days ago + read: true, + link: '/dashboards/project', + useRouter: true, + }, +]; diff --git a/src/app/mock-api/common/search/api.ts b/src/app/mock-api/common/search/api.ts new file mode 100644 index 0000000..3f66a3c --- /dev/null +++ b/src/app/mock-api/common/search/api.ts @@ -0,0 +1,136 @@ +import { Injectable } from '@angular/core'; +import { + AngorNavigationItem, + AngorNavigationService, +} from '@angor/components/navigation'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { contacts } from 'app/mock-api/apps/contacts/data'; +import { tasks } from 'app/mock-api/apps/tasks/data'; +import { defaultNavigation } from 'app/mock-api/common/navigation/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class SearchMockApi { + private readonly _defaultNavigation: AngorNavigationItem[] = + defaultNavigation; + private readonly _contacts: any[] = contacts; + private readonly _tasks: any[] = tasks; + + /** + * Constructor + */ + constructor( + private _angorMockApiService: AngorMockApiService, + private _angorNavigationService: AngorNavigationService + ) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // Get the flat navigation and store it + const flatNavigation = this._angorNavigationService.getFlatNavigation( + this._defaultNavigation + ); + + // ----------------------------------------------------------------------------------------------------- + // @ Search results - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPost('api/common/search') + .reply(({ request }) => { + // Get the search query + const query = cloneDeep(request.body.query.toLowerCase()); + + // If the search query is an empty string, + // return an empty array + if (query === '') { + return [200, { results: [] }]; + } + + // Filter the contacts + const contactsResults = cloneDeep(this._contacts).filter( + (contact) => contact.name.toLowerCase().includes(query) + ); + + // Filter the navigation + const pagesResults = cloneDeep(flatNavigation).filter( + (page) => + page.title?.toLowerCase().includes(query) || + (page.subtitle && page.subtitle.includes(query)) + ); + + // Filter the tasks + const tasksResults = cloneDeep(this._tasks).filter((task) => + task.title.toLowerCase().includes(query) + ); + + // Prepare the results array + const results = []; + + // If there are contacts results... + if (contactsResults.length > 0) { + // Normalize the results + contactsResults.forEach((result) => { + // Add a link + result.link = '/apps/contacts/' + result.id; + + // Add the name as the value + result.value = result.name; + }); + + // Add to the results + results.push({ + id: 'contacts', + label: 'Contacts', + results: contactsResults, + }); + } + + // If there are page results... + if (pagesResults.length > 0) { + // Normalize the results + pagesResults.forEach((result: any) => { + // Add the page title as the value + result.value = result.title; + }); + + // Add to the results + results.push({ + id: 'pages', + label: 'Pages', + results: pagesResults, + }); + } + + // If there are tasks results... + if (tasksResults.length > 0) { + // Normalize the results + tasksResults.forEach((result) => { + // Add a link + result.link = '/apps/tasks/' + result.id; + + // Add the title as the value + result.value = result.title; + }); + + // Add to the results + results.push({ + id: 'tasks', + label: 'Tasks', + results: tasksResults, + }); + } + + // Return the response + return [200, results]; + }); + } +} diff --git a/src/app/mock-api/common/user/api.ts b/src/app/mock-api/common/user/api.ts new file mode 100644 index 0000000..420ad1e --- /dev/null +++ b/src/app/mock-api/common/user/api.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { user as userData } from 'app/mock-api/common/user/data'; +import { assign, cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class UserMockApi { + private _user: any = userData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ User - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/common/user') + .reply(() => [200, cloneDeep(this._user)]); + + // ----------------------------------------------------------------------------------------------------- + // @ User - PATCH + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onPatch('api/common/user') + .reply(({ request }) => { + // Get the user mock-api + const user = cloneDeep(request.body.user); + + // Update the user mock-api + this._user = assign({}, this._user, user); + + // Return the response + return [200, cloneDeep(this._user)]; + }); + } +} diff --git a/src/app/mock-api/common/user/data.ts b/src/app/mock-api/common/user/data.ts new file mode 100644 index 0000000..312f29a --- /dev/null +++ b/src/app/mock-api/common/user/data.ts @@ -0,0 +1,8 @@ +/* eslint-disable */ +export const user = { + id: 'cfaad35d-07a3-4447-a6c3-d8c3d54fd5df', + name: 'Username', + email: 'username@angor.io', + avatar: 'images/avatars/username.jpg', + status: 'online', +}; diff --git a/src/app/mock-api/dashboards/analytics/api.ts b/src/app/mock-api/dashboards/analytics/api.ts new file mode 100644 index 0000000..765359c --- /dev/null +++ b/src/app/mock-api/dashboards/analytics/api.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { analytics as analyticsData } from 'app/mock-api/dashboards/analytics/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class AnalyticsMockApi { + private _analytics: any = analyticsData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Sales - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/dashboards/analytics') + .reply(() => [200, cloneDeep(this._analytics)]); + } +} diff --git a/src/app/mock-api/dashboards/analytics/data.ts b/src/app/mock-api/dashboards/analytics/data.ts new file mode 100644 index 0000000..6920135 --- /dev/null +++ b/src/app/mock-api/dashboards/analytics/data.ts @@ -0,0 +1,2338 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const analytics = { + visitors: { + series: { + 'this-year': [ + { + name: 'Visitors', + data: [ + { + x: now + .minus({ months: 12 }) + .plus({ day: 1 }) + .toJSDate(), + y: 4884, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 4 }) + .toJSDate(), + y: 5351, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 7 }) + .toJSDate(), + y: 5293, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 10 }) + .toJSDate(), + y: 4908, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 13 }) + .toJSDate(), + y: 5027, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 16 }) + .toJSDate(), + y: 4837, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 19 }) + .toJSDate(), + y: 4484, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 22 }) + .toJSDate(), + y: 4071, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 25 }) + .toJSDate(), + y: 4124, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 28 }) + .toJSDate(), + y: 4563, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 1 }) + .toJSDate(), + y: 3820, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 4 }) + .toJSDate(), + y: 3968, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 7 }) + .toJSDate(), + y: 4102, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3941, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3566, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3853, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 19 }) + .toJSDate(), + y: 3853, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 22 }) + .toJSDate(), + y: 4069, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3879, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 28 }) + .toJSDate(), + y: 4298, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 1 }) + .toJSDate(), + y: 4355, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 4 }) + .toJSDate(), + y: 4065, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3650, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3379, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3191, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 16 }) + .toJSDate(), + y: 2968, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2957, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3313, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3708, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3586, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 1 }) + .toJSDate(), + y: 3965, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 4 }) + .toJSDate(), + y: 3901, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3410, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3748, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3929, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3846, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 19 }) + .toJSDate(), + y: 3771, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 22 }) + .toJSDate(), + y: 4015, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3589, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3150, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 1 }) + .toJSDate(), + y: 3050, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2574, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 7 }) + .toJSDate(), + y: 2823, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 10 }) + .toJSDate(), + y: 2848, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3000, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3216, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 19 }) + .toJSDate(), + y: 3299, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3768, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3524, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3918, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 1 }) + .toJSDate(), + y: 4145, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 4 }) + .toJSDate(), + y: 4378, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3941, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3932, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 13 }) + .toJSDate(), + y: 4380, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 16 }) + .toJSDate(), + y: 4243, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 19 }) + .toJSDate(), + y: 4367, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3879, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 25 }) + .toJSDate(), + y: 4357, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 28 }) + .toJSDate(), + y: 4181, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 1 }) + .toJSDate(), + y: 4619, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 4 }) + .toJSDate(), + y: 4769, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 7 }) + .toJSDate(), + y: 4901, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 10 }) + .toJSDate(), + y: 4640, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 13 }) + .toJSDate(), + y: 5128, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 16 }) + .toJSDate(), + y: 5015, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 19 }) + .toJSDate(), + y: 5360, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 22 }) + .toJSDate(), + y: 5608, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 25 }) + .toJSDate(), + y: 5272, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 28 }) + .toJSDate(), + y: 5660, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 1 }) + .toJSDate(), + y: 5836, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 4 }) + .toJSDate(), + y: 5659, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 7 }) + .toJSDate(), + y: 5575, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 10 }) + .toJSDate(), + y: 5474, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 13 }) + .toJSDate(), + y: 5427, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 16 }) + .toJSDate(), + y: 5865, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 19 }) + .toJSDate(), + y: 5700, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 22 }) + .toJSDate(), + y: 6052, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 25 }) + .toJSDate(), + y: 5760, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 28 }) + .toJSDate(), + y: 5648, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 1 }) + .toJSDate(), + y: 5435, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 4 }) + .toJSDate(), + y: 5239, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 7 }) + .toJSDate(), + y: 5452, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 10 }) + .toJSDate(), + y: 5416, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 13 }) + .toJSDate(), + y: 5195, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 16 }) + .toJSDate(), + y: 5119, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 19 }) + .toJSDate(), + y: 4635, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 22 }) + .toJSDate(), + y: 4833, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 25 }) + .toJSDate(), + y: 4584, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 28 }) + .toJSDate(), + y: 4822, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 1 }) + .toJSDate(), + y: 4582, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 4 }) + .toJSDate(), + y: 4348, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 7 }) + .toJSDate(), + y: 4132, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 10 }) + .toJSDate(), + y: 4099, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3849, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 16 }) + .toJSDate(), + y: 4010, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 19 }) + .toJSDate(), + y: 4486, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 22 }) + .toJSDate(), + y: 4403, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 25 }) + .toJSDate(), + y: 4141, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3780, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 1 }) + .toJSDate(), + y: 3524, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 4 }) + .toJSDate(), + y: 3212, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3568, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3800, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3796, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3870, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 19 }) + .toJSDate(), + y: 3745, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3751, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3310, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3509, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 1 }) + .toJSDate(), + y: 3187, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2918, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3191, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3437, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3291, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3317, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 19 }) + .toJSDate(), + y: 3716, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3260, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 25 }) + .toJSDate(), + y: 3694, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 28 }) + .toJSDate(), + y: 3598, + }, + ], + }, + ], + 'last-year': [ + { + name: 'Visitors', + data: [ + { + x: now + .minus({ months: 24 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2021, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 4 }) + .toJSDate(), + y: 1749, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 7 }) + .toJSDate(), + y: 1654, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1900, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 13 }) + .toJSDate(), + y: 1647, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1315, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1807, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1793, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 25 }) + .toJSDate(), + y: 1892, + }, + { + x: now + .minus({ months: 24 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1846, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 1 }) + .toJSDate(), + y: 1804, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 4 }) + .toJSDate(), + y: 1778, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 7 }) + .toJSDate(), + y: 2015, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1892, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 13 }) + .toJSDate(), + y: 1708, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1711, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1570, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1507, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 25 }) + .toJSDate(), + y: 1451, + }, + { + x: now + .minus({ months: 23 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1522, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 1 }) + .toJSDate(), + y: 1977, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2367, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 7 }) + .toJSDate(), + y: 2798, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3080, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2856, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 16 }) + .toJSDate(), + y: 2745, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2750, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 22 }) + .toJSDate(), + y: 2728, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2436, + }, + { + x: now + .minus({ months: 22 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2289, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2804, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2777, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3024, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 10 }) + .toJSDate(), + y: 2657, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2218, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1964, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1674, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1721, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2005, + }, + { + x: now + .minus({ months: 21 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1613, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 1 }) + .toJSDate(), + y: 1071, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 4 }) + .toJSDate(), + y: 1079, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 7 }) + .toJSDate(), + y: 1133, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1536, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2016, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 16 }) + .toJSDate(), + y: 2256, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1934, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1832, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2075, + }, + { + x: now + .minus({ months: 20 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1709, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 1 }) + .toJSDate(), + y: 1831, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 4 }) + .toJSDate(), + y: 1434, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 7 }) + .toJSDate(), + y: 1293, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1064, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 13 }) + .toJSDate(), + y: 1080, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1032, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1280, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1344, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 25 }) + .toJSDate(), + y: 1835, + }, + { + x: now + .minus({ months: 19 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2287, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2692, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2250, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 7 }) + .toJSDate(), + y: 1814, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1906, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 13 }) + .toJSDate(), + y: 1973, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1882, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2333, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 22 }) + .toJSDate(), + y: 2048, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2547, + }, + { + x: now + .minus({ months: 18 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2884, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2771, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2522, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 7 }) + .toJSDate(), + y: 2543, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 10 }) + .toJSDate(), + y: 2413, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2002, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1838, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1830, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1872, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2246, + }, + { + x: now + .minus({ months: 17 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2171, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2988, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2694, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 7 }) + .toJSDate(), + y: 2806, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3040, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2898, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 16 }) + .toJSDate(), + y: 3013, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2760, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3021, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2688, + }, + { + x: now + .minus({ months: 16 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2572, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2789, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 4 }) + .toJSDate(), + y: 3069, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3142, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 10 }) + .toJSDate(), + y: 3614, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 13 }) + .toJSDate(), + y: 3202, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 16 }) + .toJSDate(), + y: 2730, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2951, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 22 }) + .toJSDate(), + y: 3267, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2882, + }, + { + x: now + .minus({ months: 15 }) + .plus({ day: 28 }) + .toJSDate(), + y: 2885, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 1 }) + .toJSDate(), + y: 2915, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 4 }) + .toJSDate(), + y: 2790, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 7 }) + .toJSDate(), + y: 3071, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 10 }) + .toJSDate(), + y: 2802, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 13 }) + .toJSDate(), + y: 2382, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 16 }) + .toJSDate(), + y: 1883, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 19 }) + .toJSDate(), + y: 1448, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 22 }) + .toJSDate(), + y: 1176, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 25 }) + .toJSDate(), + y: 1275, + }, + { + x: now + .minus({ months: 14 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1136, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 1 }) + .toJSDate(), + y: 1160, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 4 }) + .toJSDate(), + y: 1524, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 7 }) + .toJSDate(), + y: 1305, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 10 }) + .toJSDate(), + y: 1725, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 13 }) + .toJSDate(), + y: 1850, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 16 }) + .toJSDate(), + y: 2304, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 19 }) + .toJSDate(), + y: 2187, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 22 }) + .toJSDate(), + y: 2597, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 25 }) + .toJSDate(), + y: 2246, + }, + { + x: now + .minus({ months: 13 }) + .plus({ day: 28 }) + .toJSDate(), + y: 1767, + }, + ], + }, + ], + }, + }, + conversions: { + amount: 4123, + labels: [ + now.minus({ days: 47 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 40 }).toFormat('dd MMM'), + now.minus({ days: 39 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 32 }).toFormat('dd MMM'), + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Conversions', + data: [4412, 4345, 4541, 4677, 4322, 4123], + }, + ], + }, + impressions: { + amount: 46085, + labels: [ + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Impressions', + data: [11577, 11441, 11544, 11523], + }, + ], + }, + visits: { + amount: 62083, + labels: [ + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Visits', + data: [15521, 15519, 15522, 15521], + }, + ], + }, + visitorsVsPageViews: { + overallScore: 472, + averageRatio: 45, + predictedRatio: 55, + series: [ + { + name: 'Page Views', + data: [ + { + x: now.minus({ days: 65 }).toJSDate(), + y: 4769, + }, + { + x: now.minus({ days: 64 }).toJSDate(), + y: 4901, + }, + { + x: now.minus({ days: 63 }).toJSDate(), + y: 4640, + }, + { + x: now.minus({ days: 62 }).toJSDate(), + y: 5128, + }, + { + x: now.minus({ days: 61 }).toJSDate(), + y: 5015, + }, + { + x: now.minus({ days: 60 }).toJSDate(), + y: 5360, + }, + { + x: now.minus({ days: 59 }).toJSDate(), + y: 5608, + }, + { + x: now.minus({ days: 58 }).toJSDate(), + y: 5272, + }, + { + x: now.minus({ days: 57 }).toJSDate(), + y: 5660, + }, + { + x: now.minus({ days: 56 }).toJSDate(), + y: 6026, + }, + { + x: now.minus({ days: 55 }).toJSDate(), + y: 5836, + }, + { + x: now.minus({ days: 54 }).toJSDate(), + y: 5659, + }, + { + x: now.minus({ days: 53 }).toJSDate(), + y: 5575, + }, + { + x: now.minus({ days: 52 }).toJSDate(), + y: 5474, + }, + { + x: now.minus({ days: 51 }).toJSDate(), + y: 5427, + }, + { + x: now.minus({ days: 50 }).toJSDate(), + y: 5865, + }, + { + x: now.minus({ days: 49 }).toJSDate(), + y: 5700, + }, + { + x: now.minus({ days: 48 }).toJSDate(), + y: 6052, + }, + { + x: now.minus({ days: 47 }).toJSDate(), + y: 5760, + }, + { + x: now.minus({ days: 46 }).toJSDate(), + y: 5648, + }, + { + x: now.minus({ days: 45 }).toJSDate(), + y: 5510, + }, + { + x: now.minus({ days: 44 }).toJSDate(), + y: 5435, + }, + { + x: now.minus({ days: 43 }).toJSDate(), + y: 5239, + }, + { + x: now.minus({ days: 42 }).toJSDate(), + y: 5452, + }, + { + x: now.minus({ days: 41 }).toJSDate(), + y: 5416, + }, + { + x: now.minus({ days: 40 }).toJSDate(), + y: 5195, + }, + { + x: now.minus({ days: 39 }).toJSDate(), + y: 5119, + }, + { + x: now.minus({ days: 38 }).toJSDate(), + y: 4635, + }, + { + x: now.minus({ days: 37 }).toJSDate(), + y: 4833, + }, + { + x: now.minus({ days: 36 }).toJSDate(), + y: 4584, + }, + { + x: now.minus({ days: 35 }).toJSDate(), + y: 4822, + }, + { + x: now.minus({ days: 34 }).toJSDate(), + y: 4330, + }, + { + x: now.minus({ days: 33 }).toJSDate(), + y: 4582, + }, + { + x: now.minus({ days: 32 }).toJSDate(), + y: 4348, + }, + { + x: now.minus({ days: 31 }).toJSDate(), + y: 4132, + }, + { + x: now.minus({ days: 30 }).toJSDate(), + y: 4099, + }, + { + x: now.minus({ days: 29 }).toJSDate(), + y: 3849, + }, + { + x: now.minus({ days: 28 }).toJSDate(), + y: 4010, + }, + { + x: now.minus({ days: 27 }).toJSDate(), + y: 4486, + }, + { + x: now.minus({ days: 26 }).toJSDate(), + y: 4403, + }, + { + x: now.minus({ days: 25 }).toJSDate(), + y: 4141, + }, + { + x: now.minus({ days: 24 }).toJSDate(), + y: 3780, + }, + { + x: now.minus({ days: 23 }).toJSDate(), + y: 3929, + }, + { + x: now.minus({ days: 22 }).toJSDate(), + y: 3524, + }, + { + x: now.minus({ days: 21 }).toJSDate(), + y: 3212, + }, + { + x: now.minus({ days: 20 }).toJSDate(), + y: 3568, + }, + { + x: now.minus({ days: 19 }).toJSDate(), + y: 3800, + }, + { + x: now.minus({ days: 18 }).toJSDate(), + y: 3796, + }, + { + x: now.minus({ days: 17 }).toJSDate(), + y: 3870, + }, + { + x: now.minus({ days: 16 }).toJSDate(), + y: 3745, + }, + { + x: now.minus({ days: 15 }).toJSDate(), + y: 3751, + }, + { + x: now.minus({ days: 14 }).toJSDate(), + y: 3310, + }, + { + x: now.minus({ days: 13 }).toJSDate(), + y: 3509, + }, + { + x: now.minus({ days: 12 }).toJSDate(), + y: 3311, + }, + { + x: now.minus({ days: 11 }).toJSDate(), + y: 3187, + }, + { + x: now.minus({ days: 10 }).toJSDate(), + y: 2918, + }, + { + x: now.minus({ days: 9 }).toJSDate(), + y: 3191, + }, + { + x: now.minus({ days: 8 }).toJSDate(), + y: 3437, + }, + { + x: now.minus({ days: 7 }).toJSDate(), + y: 3291, + }, + { + x: now.minus({ days: 6 }).toJSDate(), + y: 3317, + }, + { + x: now.minus({ days: 5 }).toJSDate(), + y: 3716, + }, + { + x: now.minus({ days: 4 }).toJSDate(), + y: 3260, + }, + { + x: now.minus({ days: 3 }).toJSDate(), + y: 3694, + }, + { + x: now.minus({ days: 2 }).toJSDate(), + y: 3598, + }, + { + x: now.minus({ days: 1 }).toJSDate(), + y: 3812, + }, + ], + }, + { + name: 'Visitors', + data: [ + { + x: now.minus({ days: 65 }).toJSDate(), + y: 1654, + }, + { + x: now.minus({ days: 64 }).toJSDate(), + y: 1900, + }, + { + x: now.minus({ days: 63 }).toJSDate(), + y: 1647, + }, + { + x: now.minus({ days: 62 }).toJSDate(), + y: 1315, + }, + { + x: now.minus({ days: 61 }).toJSDate(), + y: 1807, + }, + { + x: now.minus({ days: 60 }).toJSDate(), + y: 1793, + }, + { + x: now.minus({ days: 59 }).toJSDate(), + y: 1892, + }, + { + x: now.minus({ days: 58 }).toJSDate(), + y: 1846, + }, + { + x: now.minus({ days: 57 }).toJSDate(), + y: 1966, + }, + { + x: now.minus({ days: 56 }).toJSDate(), + y: 1804, + }, + { + x: now.minus({ days: 55 }).toJSDate(), + y: 1778, + }, + { + x: now.minus({ days: 54 }).toJSDate(), + y: 2015, + }, + { + x: now.minus({ days: 53 }).toJSDate(), + y: 1892, + }, + { + x: now.minus({ days: 52 }).toJSDate(), + y: 1708, + }, + { + x: now.minus({ days: 51 }).toJSDate(), + y: 1711, + }, + { + x: now.minus({ days: 50 }).toJSDate(), + y: 1570, + }, + { + x: now.minus({ days: 49 }).toJSDate(), + y: 1507, + }, + { + x: now.minus({ days: 48 }).toJSDate(), + y: 1451, + }, + { + x: now.minus({ days: 47 }).toJSDate(), + y: 1522, + }, + { + x: now.minus({ days: 46 }).toJSDate(), + y: 1801, + }, + { + x: now.minus({ days: 45 }).toJSDate(), + y: 1977, + }, + { + x: now.minus({ days: 44 }).toJSDate(), + y: 2367, + }, + { + x: now.minus({ days: 43 }).toJSDate(), + y: 2798, + }, + { + x: now.minus({ days: 42 }).toJSDate(), + y: 3080, + }, + { + x: now.minus({ days: 41 }).toJSDate(), + y: 2856, + }, + { + x: now.minus({ days: 40 }).toJSDate(), + y: 2745, + }, + { + x: now.minus({ days: 39 }).toJSDate(), + y: 2750, + }, + { + x: now.minus({ days: 38 }).toJSDate(), + y: 2728, + }, + { + x: now.minus({ days: 37 }).toJSDate(), + y: 2436, + }, + { + x: now.minus({ days: 36 }).toJSDate(), + y: 2289, + }, + { + x: now.minus({ days: 35 }).toJSDate(), + y: 2657, + }, + { + x: now.minus({ days: 34 }).toJSDate(), + y: 2804, + }, + { + x: now.minus({ days: 33 }).toJSDate(), + y: 2777, + }, + { + x: now.minus({ days: 32 }).toJSDate(), + y: 3024, + }, + { + x: now.minus({ days: 31 }).toJSDate(), + y: 2657, + }, + { + x: now.minus({ days: 30 }).toJSDate(), + y: 2218, + }, + { + x: now.minus({ days: 29 }).toJSDate(), + y: 1964, + }, + { + x: now.minus({ days: 28 }).toJSDate(), + y: 1674, + }, + { + x: now.minus({ days: 27 }).toJSDate(), + y: 1721, + }, + { + x: now.minus({ days: 26 }).toJSDate(), + y: 2005, + }, + { + x: now.minus({ days: 25 }).toJSDate(), + y: 1613, + }, + { + x: now.minus({ days: 24 }).toJSDate(), + y: 1295, + }, + { + x: now.minus({ days: 23 }).toJSDate(), + y: 1071, + }, + { + x: now.minus({ days: 22 }).toJSDate(), + y: 799, + }, + { + x: now.minus({ days: 21 }).toJSDate(), + y: 1133, + }, + { + x: now.minus({ days: 20 }).toJSDate(), + y: 1536, + }, + { + x: now.minus({ days: 19 }).toJSDate(), + y: 2016, + }, + { + x: now.minus({ days: 18 }).toJSDate(), + y: 2256, + }, + { + x: now.minus({ days: 17 }).toJSDate(), + y: 1934, + }, + { + x: now.minus({ days: 16 }).toJSDate(), + y: 1832, + }, + { + x: now.minus({ days: 15 }).toJSDate(), + y: 2075, + }, + { + x: now.minus({ days: 14 }).toJSDate(), + y: 1709, + }, + { + x: now.minus({ days: 13 }).toJSDate(), + y: 1932, + }, + { + x: now.minus({ days: 12 }).toJSDate(), + y: 1831, + }, + { + x: now.minus({ days: 11 }).toJSDate(), + y: 1434, + }, + { + x: now.minus({ days: 10 }).toJSDate(), + y: 993, + }, + { + x: now.minus({ days: 9 }).toJSDate(), + y: 1064, + }, + { + x: now.minus({ days: 8 }).toJSDate(), + y: 618, + }, + { + x: now.minus({ days: 7 }).toJSDate(), + y: 1032, + }, + { + x: now.minus({ days: 6 }).toJSDate(), + y: 1280, + }, + { + x: now.minus({ days: 5 }).toJSDate(), + y: 1344, + }, + { + x: now.minus({ days: 4 }).toJSDate(), + y: 1835, + }, + { + x: now.minus({ days: 3 }).toJSDate(), + y: 2287, + }, + { + x: now.minus({ days: 2 }).toJSDate(), + y: 2226, + }, + { + x: now.minus({ days: 1 }).toJSDate(), + y: 2692, + }, + ], + }, + ], + }, + newVsReturning: { + uniqueVisitors: 46085, + series: [80, 20], + labels: ['New', 'Returning'], + }, + gender: { + uniqueVisitors: 46085, + series: [55, 45], + labels: ['Male', 'Female'], + }, + age: { + uniqueVisitors: 46085, + series: [35, 65], + labels: ['Under 30', 'Over 30'], + }, + language: { + uniqueVisitors: 46085, + series: [25, 75], + labels: ['English', 'Other'], + }, +}; diff --git a/src/app/mock-api/dashboards/crypto/api.ts b/src/app/mock-api/dashboards/crypto/api.ts new file mode 100644 index 0000000..a1462f7 --- /dev/null +++ b/src/app/mock-api/dashboards/crypto/api.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { crypto as cryptoData } from 'app/mock-api/dashboards/crypto/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class CryptoMockApi { + private _crypto: any = cryptoData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Crypto - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/dashboards/crypto') + .reply(() => [200, cloneDeep(this._crypto)]); + } +} diff --git a/src/app/mock-api/dashboards/crypto/data.ts b/src/app/mock-api/dashboards/crypto/data.ts new file mode 100644 index 0000000..0571b68 --- /dev/null +++ b/src/app/mock-api/dashboards/crypto/data.ts @@ -0,0 +1,1198 @@ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +/* tslint:disable:max-line-length */ +export const crypto = { + btc: { + amount: 8878.48, + trend: { + dir: 'up', + amount: 0.17, + }, + marketCap: 148752956966, + volume: 22903438381, + supply: 18168448, + allTimeHigh: 19891.0, + price: { + series: [ + { + name: 'Price', + data: [ + { + x: -145, + y: 6554.36, + }, + { + x: -144, + y: 6554.36, + }, + { + x: -143, + y: 6546.94, + }, + { + x: -142, + y: 6546.96, + }, + { + x: -141, + y: 6546.11, + }, + { + x: -140, + y: 6550.26, + }, + { + x: -139, + y: 6546.11, + }, + { + x: -138, + y: 6550.79, + }, + { + x: -137, + y: 6545.36, + }, + { + x: -136, + y: 6541.06, + }, + { + x: -135, + y: 6540.1, + }, + { + x: -134, + y: 6538.31, + }, + { + x: -133, + y: 6538.42, + }, + { + x: -132, + y: 6538.48, + }, + { + x: -131, + y: 6538.71, + }, + { + x: -130, + y: 6548.42, + }, + { + x: -129, + y: 6546.87, + }, + { + x: -128, + y: 6547.07, + }, + { + x: -127, + y: 6535.07, + }, + { + x: -126, + y: 6535.01, + }, + { + x: -125, + y: 6539.02, + }, + { + x: -124, + y: 6547.96, + }, + { + x: -123, + y: 6547.92, + }, + { + x: -122, + y: 6546.56, + }, + { + x: -121, + y: 6546.56, + }, + { + x: -120, + y: 6564.16, + }, + { + x: -119, + y: 6560.83, + }, + { + x: -118, + y: 6559.08, + }, + { + x: -117, + y: 6553.02, + }, + { + x: -116, + y: 6564.99, + }, + { + x: -115, + y: 6558.7, + }, + { + x: -114, + y: 6568.73, + }, + { + x: -113, + y: 6568.8, + }, + { + x: -112, + y: 6568.8, + }, + { + x: -111, + y: 6568.8, + }, + { + x: -110, + y: 6571.83, + }, + { + x: -109, + y: 6562.64, + }, + { + x: -108, + y: 6561.28, + }, + { + x: -107, + y: 6561.28, + }, + { + x: -106, + y: 6560.4, + }, + { + x: -105, + y: 6564.41, + }, + { + x: -104, + y: 6562.44, + }, + { + x: -103, + y: 6565.13, + }, + { + x: -102, + y: 6553.3, + }, + { + x: -101, + y: 6552.68, + }, + { + x: -100, + y: 6551.92, + }, + { + x: -99, + y: 6553.85, + }, + { + x: -98, + y: 6560.0, + }, + { + x: -97, + y: 6560.0, + }, + { + x: -96, + y: 6565.01, + }, + { + x: -95, + y: 6583.19, + }, + { + x: -94, + y: 6555.79, + }, + { + x: -93, + y: 6556.04, + }, + { + x: -92, + y: 6558.85, + }, + { + x: -91, + y: 6564.75, + }, + { + x: -90, + y: 6564.88, + }, + { + x: -89, + y: 6565.1, + }, + { + x: -88, + y: 6565.72, + }, + { + x: -87, + y: 6565.72, + }, + { + x: -86, + y: 6565.95, + }, + { + x: -85, + y: 6561.82, + }, + { + x: -84, + y: 6566.26, + }, + { + x: -83, + y: 6568.81, + }, + { + x: -82, + y: 6588.57, + }, + { + x: -81, + y: 6587.11, + }, + { + x: -80, + y: 6577.86, + }, + { + x: -79, + y: 6586.51, + }, + { + x: -78, + y: 6581.14, + }, + { + x: -77, + y: 6581.45, + }, + { + x: -76, + y: 6589.54, + }, + { + x: -75, + y: 6580.91, + }, + { + x: -74, + y: 6581.67, + }, + { + x: -73, + y: 6579.06, + }, + { + x: -72, + y: 6578.73, + }, + { + x: -71, + y: 6578.64, + }, + { + x: -70, + y: 6579.08, + }, + { + x: -69, + y: 6577.43, + }, + { + x: -68, + y: 6582.12, + }, + { + x: -67, + y: 6572.42, + }, + { + x: -66, + y: 6578.72, + }, + { + x: -65, + y: 6572.43, + }, + { + x: -64, + y: 6570.64, + }, + { + x: -63, + y: 6561.64, + }, + { + x: -62, + y: 6550.84, + }, + { + x: -61, + y: 6561.83, + }, + { + x: -60, + y: 6561.84, + }, + { + x: -59, + y: 6552.44, + }, + { + x: -58, + y: 6552.47, + }, + { + x: -57, + y: 6562.31, + }, + { + x: -56, + y: 6562.1, + }, + { + x: -55, + y: 6561.65, + }, + { + x: -54, + y: 6547.96, + }, + { + x: -53, + y: 6559.95, + }, + { + x: -52, + y: 6562.08, + }, + { + x: -51, + y: 6557.71, + }, + { + x: -50, + y: 6559.05, + }, + { + x: -49, + y: 6562.69, + }, + { + x: -48, + y: 6578.18, + }, + { + x: -47, + y: 6580.15, + }, + { + x: -46, + y: 6584.26, + }, + { + x: -45, + y: 6574.75, + }, + { + x: -44, + y: 6574.85, + }, + { + x: -43, + y: 6582.63, + }, + { + x: -42, + y: 6569.7, + }, + { + x: -41, + y: 6570.1, + }, + { + x: -40, + y: 6570.11, + }, + { + x: -39, + y: 6569.71, + }, + { + x: -38, + y: 6578.03, + }, + { + x: -37, + y: 6579.92, + }, + { + x: -36, + y: 6571.03, + }, + { + x: -35, + y: 6571.48, + }, + { + x: -34, + y: 6576.67, + }, + { + x: -33, + y: 6576.67, + }, + { + x: -32, + y: 6576.63, + }, + { + x: -31, + y: 6576.68, + }, + { + x: -30, + y: 6573.29, + }, + { + x: -29, + y: 6577.28, + }, + { + x: -28, + y: 6577.73, + }, + { + x: -27, + y: 6577.7, + }, + { + x: -26, + y: 6578.36, + }, + { + x: -25, + y: 6578.24, + }, + { + x: -24, + y: 6581.3, + }, + { + x: -23, + y: 6582.59, + }, + { + x: -22, + y: 6602.51, + }, + { + x: -21, + y: 6582.65, + }, + { + x: -20, + y: 6574.77, + }, + { + x: -19, + y: 6574.41, + }, + { + x: -18, + y: 6575.08, + }, + { + x: -17, + y: 6575.08, + }, + { + x: -16, + y: 6574.09, + }, + { + x: -15, + y: 6568.84, + }, + { + x: -14, + y: 6567.49, + }, + { + x: -13, + y: 6559.75, + }, + { + x: -12, + y: 6566.65, + }, + { + x: -11, + y: 6567.52, + }, + { + x: -10, + y: 6567.59, + }, + { + x: -9, + y: 6564.18, + }, + { + x: -8, + y: 6570.11, + }, + { + x: -7, + y: 6562.7, + }, + { + x: -6, + y: 6562.7, + }, + { + x: -5, + y: 6562.77, + }, + { + x: -4, + y: 6569.46, + }, + { + x: -3, + y: 6571.04, + }, + { + x: -2, + y: 6571.48, + }, + { + x: -1, + y: 6571.3, + }, + ], + }, + ], + }, + }, + prices: { + btc: 8878.48, + eth: 170.46, + bch: 359.93, + xrp: 0.23512, + }, + wallets: { + btc: 24.97311243, + eth: 126.3212, + bch: 78.454412, + xrp: 11278.771123, + }, + watchlist: [ + { + title: 'Ethereum', + iso: 'ETH', + amount: 170.46, + trend: { + dir: 'up', + amount: 2.35, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 154.36, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 154.36, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 146.94, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 146.96, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 146.11, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 150.26, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 146.11, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 150.79, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 145.36, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 141.06, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 140.1, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 138.31, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 138.42, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 138.48, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 138.71, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 148.42, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 146.87, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 147.07, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 135.07, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 135.01, + }, + ], + }, + ], + }, + { + title: 'Bitcoin Cash', + iso: 'BCH', + amount: 359.93, + trend: { + dir: 'up', + amount: 9.94, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 374.77, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 374.41, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 375.08, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 375.08, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 374.09, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 368.84, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 367.49, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 359.75, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 366.65, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 367.52, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 367.59, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 364.18, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 370.11, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 362.7, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 362.7, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 362.77, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 369.46, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 371.04, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 371.48, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 371.3, + }, + ], + }, + ], + }, + { + title: 'XRP', + iso: 'XRP', + amount: 0.23512, + trend: { + dir: 'down', + amount: 0.35, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 0.258, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 0.256, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 0.255, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 0.255, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 0.254, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 0.248, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 0.247, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 0.249, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 0.246, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 0.247, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 0.247, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 0.244, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 0.25, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 0.242, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 0.251, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 0.251, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 0.251, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 0.249, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 0.242, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 0.24, + }, + ], + }, + ], + }, + { + title: 'Litecoin', + iso: 'LTC', + amount: 60.15, + trend: { + dir: 'up', + amount: 0.99, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 62.54, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 61.54, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 62.55, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 60.55, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 59.54, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 58.48, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 54.47, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 51.49, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 51.46, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 53.47, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 52.47, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 54.44, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 59.5, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 62.42, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 61.42, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 60.42, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 58.49, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 57.51, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 54.51, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 51.25, + }, + ], + }, + ], + }, + { + title: 'Zcash', + iso: 'ZEC', + amount: 58.41, + trend: { + dir: 'down', + amount: 8.79, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 53.54, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 52.54, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 52.55, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 46.44, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 49.5, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 55.42, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 54.42, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 43.49, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 43.46, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 41.47, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 41.47, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 51.55, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 48.54, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 49.48, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 45.47, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 51.42, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 49.49, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 46.51, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 41.51, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 44.25, + }, + ], + }, + ], + }, + { + title: 'Bitcoin Gold', + iso: 'BTG', + amount: 12.23, + trend: { + dir: 'down', + amount: 4.42, + }, + series: [ + { + name: 'Price', + data: [ + { + x: now.minus({ minutes: 20 }).toFormat('HH:mm'), + y: 14.77, + }, + { + x: now.minus({ minutes: 19 }).toFormat('HH:mm'), + y: 14.41, + }, + { + x: now.minus({ minutes: 18 }).toFormat('HH:mm'), + y: 15.08, + }, + { + x: now.minus({ minutes: 17 }).toFormat('HH:mm'), + y: 15.08, + }, + { + x: now.minus({ minutes: 16 }).toFormat('HH:mm'), + y: 14.09, + }, + { + x: now.minus({ minutes: 15 }).toFormat('HH:mm'), + y: 18.84, + }, + { + x: now.minus({ minutes: 14 }).toFormat('HH:mm'), + y: 17.49, + }, + { + x: now.minus({ minutes: 13 }).toFormat('HH:mm'), + y: 19.75, + }, + { + x: now.minus({ minutes: 12 }).toFormat('HH:mm'), + y: 16.65, + }, + { + x: now.minus({ minutes: 11 }).toFormat('HH:mm'), + y: 17.52, + }, + { + x: now.minus({ minutes: 10 }).toFormat('HH:mm'), + y: 17.59, + }, + { + x: now.minus({ minutes: 9 }).toFormat('HH:mm'), + y: 14.18, + }, + { + x: now.minus({ minutes: 8 }).toFormat('HH:mm'), + y: 10.11, + }, + { + x: now.minus({ minutes: 7 }).toFormat('HH:mm'), + y: 12.7, + }, + { + x: now.minus({ minutes: 6 }).toFormat('HH:mm'), + y: 12.7, + }, + { + x: now.minus({ minutes: 5 }).toFormat('HH:mm'), + y: 12.77, + }, + { + x: now.minus({ minutes: 4 }).toFormat('HH:mm'), + y: 19.46, + }, + { + x: now.minus({ minutes: 3 }).toFormat('HH:mm'), + y: 11.04, + }, + { + x: now.minus({ minutes: 2 }).toFormat('HH:mm'), + y: 11.48, + }, + { + x: now.minus({ minutes: 1 }).toFormat('HH:mm'), + y: 11.3, + }, + ], + }, + ], + }, + ], +}; diff --git a/src/app/mock-api/dashboards/finance/api.ts b/src/app/mock-api/dashboards/finance/api.ts new file mode 100644 index 0000000..9eb63c1 --- /dev/null +++ b/src/app/mock-api/dashboards/finance/api.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { finance as financeData } from 'app/mock-api/dashboards/finance/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class FinanceMockApi { + private _finance: any = financeData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Sales - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/dashboards/finance') + .reply(() => [200, cloneDeep(this._finance)]); + } +} diff --git a/src/app/mock-api/dashboards/finance/data.ts b/src/app/mock-api/dashboards/finance/data.ts new file mode 100644 index 0000000..6e7c384 --- /dev/null +++ b/src/app/mock-api/dashboards/finance/data.ts @@ -0,0 +1,1610 @@ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +/* tslint:disable:max-line-length */ +export const finance = { + accountBalance: { + growRate: 38.33, + ami: 45332, + series: [ + { + name: 'Predicted', + data: [ + { + x: now + .minus({ months: 12 }) + .plus({ day: 1 }) + .toJSDate(), + y: 48.84, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 4 }) + .toJSDate(), + y: 53.51, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 7 }) + .toJSDate(), + y: 52.93, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 10 }) + .toJSDate(), + y: 49.08, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 13 }) + .toJSDate(), + y: 50.27, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 16 }) + .toJSDate(), + y: 48.37, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 19 }) + .toJSDate(), + y: 44.84, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 22 }) + .toJSDate(), + y: 40.71, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 25 }) + .toJSDate(), + y: 41.24, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 28 }) + .toJSDate(), + y: 45.63, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 1 }) + .toJSDate(), + y: 38.2, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 4 }) + .toJSDate(), + y: 39.68, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 7 }) + .toJSDate(), + y: 41.02, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 10 }) + .toJSDate(), + y: 39.41, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 13 }) + .toJSDate(), + y: 35.66, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 16 }) + .toJSDate(), + y: 38.53, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 19 }) + .toJSDate(), + y: 38.53, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 22 }) + .toJSDate(), + y: 40.69, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 25 }) + .toJSDate(), + y: 38.79, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 28 }) + .toJSDate(), + y: 42.98, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 1 }) + .toJSDate(), + y: 43.55, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 4 }) + .toJSDate(), + y: 40.65, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 7 }) + .toJSDate(), + y: 36.5, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 10 }) + .toJSDate(), + y: 33.79, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 13 }) + .toJSDate(), + y: 31.91, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 16 }) + .toJSDate(), + y: 29.68, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 19 }) + .toJSDate(), + y: 29.57, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 22 }) + .toJSDate(), + y: 33.13, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 25 }) + .toJSDate(), + y: 37.08, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 28 }) + .toJSDate(), + y: 35.86, + }, + { + x: now.minus({ months: 9 }).plus({ day: 1 }).toJSDate(), + y: 39.65, + }, + { + x: now.minus({ months: 9 }).plus({ day: 4 }).toJSDate(), + y: 39.01, + }, + { + x: now.minus({ months: 9 }).plus({ day: 7 }).toJSDate(), + y: 34.1, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 10 }) + .toJSDate(), + y: 37.48, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 13 }) + .toJSDate(), + y: 39.29, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 16 }) + .toJSDate(), + y: 38.46, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 19 }) + .toJSDate(), + y: 37.71, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 22 }) + .toJSDate(), + y: 40.15, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 25 }) + .toJSDate(), + y: 35.89, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 28 }) + .toJSDate(), + y: 31.5, + }, + { + x: now.minus({ months: 8 }).plus({ day: 1 }).toJSDate(), + y: 30.5, + }, + { + x: now.minus({ months: 8 }).plus({ day: 4 }).toJSDate(), + y: 25.74, + }, + { + x: now.minus({ months: 8 }).plus({ day: 7 }).toJSDate(), + y: 28.23, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 10 }) + .toJSDate(), + y: 28.48, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 13 }) + .toJSDate(), + y: 30.0, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 16 }) + .toJSDate(), + y: 32.16, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 19 }) + .toJSDate(), + y: 32.99, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 22 }) + .toJSDate(), + y: 37.68, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 25 }) + .toJSDate(), + y: 35.24, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 28 }) + .toJSDate(), + y: 39.18, + }, + { + x: now.minus({ months: 7 }).plus({ day: 1 }).toJSDate(), + y: 41.45, + }, + { + x: now.minus({ months: 7 }).plus({ day: 4 }).toJSDate(), + y: 43.78, + }, + { + x: now.minus({ months: 7 }).plus({ day: 7 }).toJSDate(), + y: 39.41, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 10 }) + .toJSDate(), + y: 39.32, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 13 }) + .toJSDate(), + y: 43.8, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 16 }) + .toJSDate(), + y: 42.43, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 19 }) + .toJSDate(), + y: 43.67, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 22 }) + .toJSDate(), + y: 38.79, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 25 }) + .toJSDate(), + y: 43.57, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 28 }) + .toJSDate(), + y: 41.81, + }, + { + x: now.minus({ months: 6 }).plus({ day: 1 }).toJSDate(), + y: 46.19, + }, + { + x: now.minus({ months: 6 }).plus({ day: 4 }).toJSDate(), + y: 47.69, + }, + { + x: now.minus({ months: 6 }).plus({ day: 7 }).toJSDate(), + y: 49.01, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 10 }) + .toJSDate(), + y: 46.4, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 13 }) + .toJSDate(), + y: 51.28, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 16 }) + .toJSDate(), + y: 50.15, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 19 }) + .toJSDate(), + y: 53.6, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 22 }) + .toJSDate(), + y: 56.08, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 25 }) + .toJSDate(), + y: 52.72, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 28 }) + .toJSDate(), + y: 56.6, + }, + { + x: now.minus({ months: 5 }).plus({ day: 1 }).toJSDate(), + y: 58.36, + }, + { + x: now.minus({ months: 5 }).plus({ day: 4 }).toJSDate(), + y: 56.59, + }, + { + x: now.minus({ months: 5 }).plus({ day: 7 }).toJSDate(), + y: 55.75, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 10 }) + .toJSDate(), + y: 54.74, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 13 }) + .toJSDate(), + y: 54.27, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 16 }) + .toJSDate(), + y: 58.65, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 19 }) + .toJSDate(), + y: 57.0, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 22 }) + .toJSDate(), + y: 60.52, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 25 }) + .toJSDate(), + y: 57.6, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 28 }) + .toJSDate(), + y: 56.48, + }, + { + x: now.minus({ months: 4 }).plus({ day: 1 }).toJSDate(), + y: 54.35, + }, + { + x: now.minus({ months: 4 }).plus({ day: 4 }).toJSDate(), + y: 52.39, + }, + { + x: now.minus({ months: 4 }).plus({ day: 7 }).toJSDate(), + y: 54.52, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 10 }) + .toJSDate(), + y: 54.16, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 13 }) + .toJSDate(), + y: 51.95, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 16 }) + .toJSDate(), + y: 51.19, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 19 }) + .toJSDate(), + y: 46.35, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 22 }) + .toJSDate(), + y: 48.33, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 25 }) + .toJSDate(), + y: 45.84, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 28 }) + .toJSDate(), + y: 48.22, + }, + { + x: now.minus({ months: 3 }).plus({ day: 1 }).toJSDate(), + y: 45.82, + }, + { + x: now.minus({ months: 3 }).plus({ day: 4 }).toJSDate(), + y: 43.48, + }, + { + x: now.minus({ months: 3 }).plus({ day: 7 }).toJSDate(), + y: 41.32, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 10 }) + .toJSDate(), + y: 40.99, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 13 }) + .toJSDate(), + y: 38.49, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 16 }) + .toJSDate(), + y: 40.1, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 19 }) + .toJSDate(), + y: 44.86, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 22 }) + .toJSDate(), + y: 44.03, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 25 }) + .toJSDate(), + y: 41.41, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 28 }) + .toJSDate(), + y: 37.8, + }, + { + x: now.minus({ months: 2 }).plus({ day: 1 }).toJSDate(), + y: 35.24, + }, + { + x: now.minus({ months: 2 }).plus({ day: 4 }).toJSDate(), + y: 32.12, + }, + { + x: now.minus({ months: 2 }).plus({ day: 7 }).toJSDate(), + y: 35.68, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 10 }) + .toJSDate(), + y: 38.0, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 13 }) + .toJSDate(), + y: 37.96, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 16 }) + .toJSDate(), + y: 38.7, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 19 }) + .toJSDate(), + y: 37.45, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 22 }) + .toJSDate(), + y: 37.51, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 25 }) + .toJSDate(), + y: 33.1, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 28 }) + .toJSDate(), + y: 35.09, + }, + { + x: now.minus({ months: 1 }).plus({ day: 1 }).toJSDate(), + y: 31.87, + }, + { + x: now.minus({ months: 1 }).plus({ day: 4 }).toJSDate(), + y: 29.18, + }, + { + x: now.minus({ months: 1 }).plus({ day: 7 }).toJSDate(), + y: 31.91, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 10 }) + .toJSDate(), + y: 34.37, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 13 }) + .toJSDate(), + y: 32.91, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 16 }) + .toJSDate(), + y: 33.17, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 19 }) + .toJSDate(), + y: 37.16, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 22 }) + .toJSDate(), + y: 32.6, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 25 }) + .toJSDate(), + y: 36.94, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 28 }) + .toJSDate(), + y: 35.98, + }, + ], + }, + { + name: 'Actual', + data: [ + { + x: now + .minus({ months: 12 }) + .plus({ day: 1 }) + .toJSDate(), + y: 20.21, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 4 }) + .toJSDate(), + y: 17.49, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 7 }) + .toJSDate(), + y: 16.54, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 10 }) + .toJSDate(), + y: 19.0, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 13 }) + .toJSDate(), + y: 16.47, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 16 }) + .toJSDate(), + y: 13.15, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 19 }) + .toJSDate(), + y: 18.07, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 22 }) + .toJSDate(), + y: 17.93, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 25 }) + .toJSDate(), + y: 18.92, + }, + { + x: now + .minus({ months: 12 }) + .plus({ day: 28 }) + .toJSDate(), + y: 18.46, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 1 }) + .toJSDate(), + y: 18.04, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 4 }) + .toJSDate(), + y: 17.78, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 7 }) + .toJSDate(), + y: 20.15, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 10 }) + .toJSDate(), + y: 18.92, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 13 }) + .toJSDate(), + y: 17.08, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 16 }) + .toJSDate(), + y: 17.11, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 19 }) + .toJSDate(), + y: 15.7, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 22 }) + .toJSDate(), + y: 15.07, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 25 }) + .toJSDate(), + y: 14.51, + }, + { + x: now + .minus({ months: 11 }) + .plus({ day: 28 }) + .toJSDate(), + y: 15.22, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 1 }) + .toJSDate(), + y: 19.77, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 4 }) + .toJSDate(), + y: 23.67, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 7 }) + .toJSDate(), + y: 27.98, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 10 }) + .toJSDate(), + y: 30.8, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 13 }) + .toJSDate(), + y: 28.56, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 16 }) + .toJSDate(), + y: 27.45, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 19 }) + .toJSDate(), + y: 27.5, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 22 }) + .toJSDate(), + y: 27.28, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 25 }) + .toJSDate(), + y: 24.36, + }, + { + x: now + .minus({ months: 10 }) + .plus({ day: 28 }) + .toJSDate(), + y: 22.89, + }, + { + x: now.minus({ months: 9 }).plus({ day: 1 }).toJSDate(), + y: 28.04, + }, + { + x: now.minus({ months: 9 }).plus({ day: 4 }).toJSDate(), + y: 27.77, + }, + { + x: now.minus({ months: 9 }).plus({ day: 7 }).toJSDate(), + y: 30.24, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 10 }) + .toJSDate(), + y: 26.57, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 13 }) + .toJSDate(), + y: 22.18, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 16 }) + .toJSDate(), + y: 19.64, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 19 }) + .toJSDate(), + y: 16.74, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 22 }) + .toJSDate(), + y: 17.21, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 25 }) + .toJSDate(), + y: 20.05, + }, + { + x: now + .minus({ months: 9 }) + .plus({ day: 28 }) + .toJSDate(), + y: 16.13, + }, + { + x: now.minus({ months: 8 }).plus({ day: 1 }).toJSDate(), + y: 10.71, + }, + { + x: now.minus({ months: 8 }).plus({ day: 4 }).toJSDate(), + y: 7.99, + }, + { + x: now.minus({ months: 8 }).plus({ day: 7 }).toJSDate(), + y: 11.33, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 10 }) + .toJSDate(), + y: 15.36, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 13 }) + .toJSDate(), + y: 20.16, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 16 }) + .toJSDate(), + y: 22.56, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 19 }) + .toJSDate(), + y: 19.34, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 22 }) + .toJSDate(), + y: 18.32, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 25 }) + .toJSDate(), + y: 20.75, + }, + { + x: now + .minus({ months: 8 }) + .plus({ day: 28 }) + .toJSDate(), + y: 17.09, + }, + { + x: now.minus({ months: 7 }).plus({ day: 1 }).toJSDate(), + y: 18.31, + }, + { + x: now.minus({ months: 7 }).plus({ day: 4 }).toJSDate(), + y: 14.34, + }, + { + x: now.minus({ months: 7 }).plus({ day: 7 }).toJSDate(), + y: 9.93, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 10 }) + .toJSDate(), + y: 10.64, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 13 }) + .toJSDate(), + y: 6.18, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 16 }) + .toJSDate(), + y: 10.32, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 19 }) + .toJSDate(), + y: 12.8, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 22 }) + .toJSDate(), + y: 13.44, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 25 }) + .toJSDate(), + y: 18.35, + }, + { + x: now + .minus({ months: 7 }) + .plus({ day: 28 }) + .toJSDate(), + y: 22.87, + }, + { + x: now.minus({ months: 6 }).plus({ day: 1 }).toJSDate(), + y: 26.92, + }, + { + x: now.minus({ months: 6 }).plus({ day: 4 }).toJSDate(), + y: 22.5, + }, + { + x: now.minus({ months: 6 }).plus({ day: 7 }).toJSDate(), + y: 18.14, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 10 }) + .toJSDate(), + y: 19.06, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 13 }) + .toJSDate(), + y: 19.73, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 16 }) + .toJSDate(), + y: 18.82, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 19 }) + .toJSDate(), + y: 23.33, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 22 }) + .toJSDate(), + y: 20.48, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 25 }) + .toJSDate(), + y: 25.47, + }, + { + x: now + .minus({ months: 6 }) + .plus({ day: 28 }) + .toJSDate(), + y: 28.84, + }, + { + x: now.minus({ months: 5 }).plus({ day: 1 }).toJSDate(), + y: 27.71, + }, + { + x: now.minus({ months: 5 }).plus({ day: 4 }).toJSDate(), + y: 25.22, + }, + { + x: now.minus({ months: 5 }).plus({ day: 7 }).toJSDate(), + y: 25.43, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 10 }) + .toJSDate(), + y: 24.13, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 13 }) + .toJSDate(), + y: 20.02, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 16 }) + .toJSDate(), + y: 18.38, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 19 }) + .toJSDate(), + y: 18.3, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 22 }) + .toJSDate(), + y: 18.72, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 25 }) + .toJSDate(), + y: 22.46, + }, + { + x: now + .minus({ months: 5 }) + .plus({ day: 28 }) + .toJSDate(), + y: 21.71, + }, + { + x: now.minus({ months: 4 }).plus({ day: 1 }).toJSDate(), + y: 29.88, + }, + { + x: now.minus({ months: 4 }).plus({ day: 4 }).toJSDate(), + y: 26.94, + }, + { + x: now.minus({ months: 4 }).plus({ day: 7 }).toJSDate(), + y: 28.06, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 10 }) + .toJSDate(), + y: 30.4, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 13 }) + .toJSDate(), + y: 28.98, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 16 }) + .toJSDate(), + y: 30.13, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 19 }) + .toJSDate(), + y: 27.6, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 22 }) + .toJSDate(), + y: 30.21, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 25 }) + .toJSDate(), + y: 26.88, + }, + { + x: now + .minus({ months: 4 }) + .plus({ day: 28 }) + .toJSDate(), + y: 25.72, + }, + { + x: now.minus({ months: 3 }).plus({ day: 1 }).toJSDate(), + y: 27.89, + }, + { + x: now.minus({ months: 3 }).plus({ day: 4 }).toJSDate(), + y: 30.69, + }, + { + x: now.minus({ months: 3 }).plus({ day: 7 }).toJSDate(), + y: 31.42, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 10 }) + .toJSDate(), + y: 36.14, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 13 }) + .toJSDate(), + y: 32.02, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 16 }) + .toJSDate(), + y: 27.3, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 19 }) + .toJSDate(), + y: 29.51, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 22 }) + .toJSDate(), + y: 32.67, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 25 }) + .toJSDate(), + y: 28.82, + }, + { + x: now + .minus({ months: 3 }) + .plus({ day: 28 }) + .toJSDate(), + y: 28.85, + }, + { + x: now.minus({ months: 2 }).plus({ day: 1 }).toJSDate(), + y: 29.15, + }, + { + x: now.minus({ months: 2 }).plus({ day: 4 }).toJSDate(), + y: 27.9, + }, + { + x: now.minus({ months: 2 }).plus({ day: 7 }).toJSDate(), + y: 30.71, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 10 }) + .toJSDate(), + y: 28.02, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 13 }) + .toJSDate(), + y: 23.82, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 16 }) + .toJSDate(), + y: 18.83, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 19 }) + .toJSDate(), + y: 14.48, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 22 }) + .toJSDate(), + y: 11.76, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 25 }) + .toJSDate(), + y: 12.75, + }, + { + x: now + .minus({ months: 2 }) + .plus({ day: 28 }) + .toJSDate(), + y: 11.36, + }, + { + x: now.minus({ months: 1 }).plus({ day: 1 }).toJSDate(), + y: 11.6, + }, + { + x: now.minus({ months: 1 }).plus({ day: 4 }).toJSDate(), + y: 15.24, + }, + { + x: now.minus({ months: 1 }).plus({ day: 7 }).toJSDate(), + y: 13.05, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 10 }) + .toJSDate(), + y: 17.25, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 13 }) + .toJSDate(), + y: 18.5, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 16 }) + .toJSDate(), + y: 23.04, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 19 }) + .toJSDate(), + y: 21.87, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 22 }) + .toJSDate(), + y: 25.97, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 25 }) + .toJSDate(), + y: 22.46, + }, + { + x: now + .minus({ months: 1 }) + .plus({ day: 28 }) + .toJSDate(), + y: 17.67, + }, + ], + }, + ], + }, + budget: { + expenses: 11763.34, + expensesLimit: 20000, + savings: 10974.12, + savingsGoal: 250000, + bills: 1789.22, + billsLimit: 1000, + }, + previousStatement: { + status: 'paid', + date: now.startOf('day').minus({ days: 15 }).toFormat('DDD'), + limit: 34500, + spent: 27221.21, + minimum: 7331.94, + }, + currentStatement: { + status: 'pending', + date: now + .startOf('day') + .minus({ days: 15 }) + .plus({ month: 1 }) + .toFormat('DDD'), + limit: 34500, + spent: 39819.41, + minimum: 9112.51, + }, + recentTransactions: [ + { + id: '1b6fd296-bc6a-4d45-bf4f-e45519a58cf5', + transactionId: '528651571NT', + name: 'Morgan Page', + amount: +1358.75, + status: 'completed', + date: '2019-10-07T22:22:37.274Z', + }, + { + id: '2dec6074-98bd-4623-9526-6480e4776569', + transactionId: '421436904YT', + name: 'Nita Hebert', + amount: -1042.82, + status: 'completed', + date: '2019-12-18T14:51:24.461Z', + }, + { + id: 'ae7c065f-4197-4021-a799-7a221822ad1d', + transactionId: '685377421YT', + name: 'Marsha Chambers', + amount: +1828.16, + status: 'pending', + date: '2019-12-25T17:52:14.304Z', + }, + { + id: '0c43dd40-74f6-49d5-848a-57a4a45772ab', + transactionId: '884960091RT', + name: 'Charmaine Jackson', + amount: +1647.55, + status: 'completed', + date: '2019-11-29T06:32:16.111Z', + }, + { + id: 'e5c9f0ed-a64c-4bfe-a113-29f80b4e162c', + transactionId: '361402213NT', + name: 'Maura Carey', + amount: -927.43, + status: 'completed', + date: '2019-11-24T12:13:23.064Z', + }, + ], +}; diff --git a/src/app/mock-api/dashboards/project/api.ts b/src/app/mock-api/dashboards/project/api.ts new file mode 100644 index 0000000..9e71903 --- /dev/null +++ b/src/app/mock-api/dashboards/project/api.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { project as projectData } from 'app/mock-api/dashboards/project/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class ProjectMockApi { + private _project: any = projectData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Sales - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/dashboards/project') + .reply(() => [200, cloneDeep(this._project)]); + } +} diff --git a/src/app/mock-api/dashboards/project/data.ts b/src/app/mock-api/dashboards/project/data.ts new file mode 100644 index 0000000..bfdaf93 --- /dev/null +++ b/src/app/mock-api/dashboards/project/data.ts @@ -0,0 +1,378 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const project = { + githubIssues: { + overview: { + 'this-week': { + 'new-issues': 214, + 'closed-issues': 75, + fixed: 3, + 'wont-fix': 4, + 're-opened': 8, + 'needs-triage': 6, + }, + 'last-week': { + 'new-issues': 197, + 'closed-issues': 72, + fixed: 6, + 'wont-fix': 11, + 're-opened': 6, + 'needs-triage': 5, + }, + }, + labels: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + series: { + 'this-week': [ + { + name: 'New issues', + type: 'line', + data: [42, 28, 43, 34, 20, 25, 22], + }, + { + name: 'Closed issues', + type: 'column', + data: [11, 10, 8, 11, 8, 10, 17], + }, + ], + 'last-week': [ + { + name: 'New issues', + type: 'line', + data: [37, 32, 39, 27, 18, 24, 20], + }, + { + name: 'Closed issues', + type: 'column', + data: [9, 8, 10, 12, 7, 11, 15], + }, + ], + }, + }, + taskDistribution: { + overview: { + 'this-week': { + new: 594, + completed: 287, + }, + 'last-week': { + new: 526, + completed: 260, + }, + }, + labels: ['API', 'Backend', 'Frontend', 'Issues'], + series: { + 'this-week': [15, 20, 38, 27], + 'last-week': [19, 16, 42, 23], + }, + }, + schedule: { + today: [ + { + title: 'Group Meeting', + time: 'in 32 minutes', + location: 'Conference room 1B', + }, + { + title: 'Coffee Break', + time: '10:30 AM', + }, + { + title: 'Public Beta Release', + time: '11:00 AM', + }, + { + title: 'Lunch', + time: '12:10 PM', + }, + { + title: 'Dinner with David', + time: '05:30 PM', + location: 'Magnolia', + }, + { + title: "Jane's Birthday Party", + time: '07:30 PM', + location: 'Home', + }, + { + title: "Overseer's Retirement Party", + time: '09:30 PM', + location: "Overseer's room", + }, + ], + tomorrow: [ + { + title: 'Marketing Meeting', + time: '09:00 AM', + location: 'Conference room 1A', + }, + { + title: 'Public Announcement', + time: '11:00 AM', + }, + { + title: 'Lunch', + time: '12:10 PM', + }, + { + title: 'Meeting with Beta Testers', + time: '03:00 PM', + location: 'Conference room 2C', + }, + { + title: 'Live Stream', + time: '05:30 PM', + }, + { + title: 'Release Party', + time: '07:30 PM', + location: "CEO's house", + }, + { + title: "CEO's Private Party", + time: '09:30 PM', + location: "CEO's Penthouse", + }, + ], + }, + budgetDistribution: { + categories: ['Concept', 'Design', 'Development', 'Extras', 'Marketing'], + series: [ + { + name: 'Budget', + data: [12, 20, 28, 15, 25], + }, + ], + }, + weeklyExpenses: { + amount: 17663, + labels: [ + now.minus({ days: 47 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 40 }).toFormat('dd MMM'), + now.minus({ days: 39 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 32 }).toFormat('dd MMM'), + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Expenses', + data: [4412, 4345, 4541, 4677, 4322, 4123], + }, + ], + }, + monthlyExpenses: { + amount: 54663, + labels: [ + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Expenses', + data: [15521, 15519, 15522, 15521], + }, + ], + }, + yearlyExpenses: { + amount: 648813, + labels: [ + now.minus({ days: 79 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 72 }).toFormat('dd MMM'), + now.minus({ days: 71 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 64 }).toFormat('dd MMM'), + now.minus({ days: 63 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 56 }).toFormat('dd MMM'), + now.minus({ days: 55 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 48 }).toFormat('dd MMM'), + now.minus({ days: 47 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 40 }).toFormat('dd MMM'), + now.minus({ days: 39 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 32 }).toFormat('dd MMM'), + now.minus({ days: 31 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 24 }).toFormat('dd MMM'), + now.minus({ days: 23 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 16 }).toFormat('dd MMM'), + now.minus({ days: 15 }).toFormat('dd MMM') + + ' - ' + + now.minus({ days: 8 }).toFormat('dd MMM'), + now.minus({ days: 7 }).toFormat('dd MMM') + + ' - ' + + now.toFormat('dd MMM'), + ], + series: [ + { + name: 'Expenses', + data: [ + 45891, 45801, 45834, 45843, 45800, 45900, 45814, 45856, + 45910, 45849, + ], + }, + ], + }, + budgetDetails: { + columns: [ + 'type', + 'total', + 'expensesAmount', + 'expensesPercentage', + 'remainingAmount', + 'remainingPercentage', + ], + rows: [ + { + id: 1, + type: 'Concept', + total: 14880, + expensesAmount: 14000, + expensesPercentage: 94.08, + remainingAmount: 880, + remainingPercentage: 5.92, + }, + { + id: 2, + type: 'Design', + total: 21080, + expensesAmount: 17240.34, + expensesPercentage: 81.78, + remainingAmount: 3839.66, + remainingPercentage: 18.22, + }, + { + id: 3, + type: 'Development', + total: 34720, + expensesAmount: 3518, + expensesPercentage: 10.13, + remainingAmount: 31202, + remainingPercentage: 89.87, + }, + { + id: 4, + type: 'Extras', + total: 18600, + expensesAmount: 0, + expensesPercentage: 0, + remainingAmount: 18600, + remainingPercentage: 100, + }, + { + id: 5, + type: 'Marketing', + total: 34720, + expensesAmount: 19859.84, + expensesPercentage: 57.2, + remainingAmount: 14860.16, + remainingPercentage: 42.8, + }, + ], + }, + teamMembers: [ + { + id: '2bfa2be5-7688-48d5-b5ac-dc0d9ac97f14', + avatar: 'images/avatars/female-10.jpg', + name: 'Nadia Mcknight', + email: 'nadiamcknight@mail.com', + phone: '+1-943-511-2203', + title: 'Project Director', + }, + { + id: '77a4383b-b5a5-4943-bc46-04c3431d1566', + avatar: 'images/avatars/male-19.jpg', + name: 'Best Blackburn', + email: 'blackburn.best@beadzza.me', + phone: '+1-814-498-3701', + title: 'Senior Developer', + }, + { + id: '8bb0f597-673a-47ca-8c77-2f83219cb9af', + avatar: 'images/avatars/male-14.jpg', + name: 'Duncan Carver', + email: 'duncancarver@mail.info', + phone: '+1-968-547-2111', + title: 'Senior Developer', + }, + { + id: 'c318e31f-1d74-49c5-8dae-2bc5805e2fdb', + avatar: 'images/avatars/male-01.jpg', + name: 'Martin Richards', + email: 'martinrichards@mail.biz', + phone: '+1-902-500-2668', + title: 'Junior Developer', + }, + { + id: '0a8bc517-631a-4a93-aacc-000fa2e8294c', + avatar: 'images/avatars/female-20.jpg', + name: 'Candice Munoz', + email: 'candicemunoz@mail.co.uk', + phone: '+1-838-562-2769', + title: 'Lead Designer', + }, + { + id: 'a4c9945a-757b-40b0-8942-d20e0543cabd', + avatar: 'images/avatars/female-01.jpg', + name: 'Vickie Mosley', + email: 'vickiemosley@mail.net', + phone: '+1-939-555-3054', + title: 'Designer', + }, + { + id: 'b8258ccf-48b5-46a2-9c95-e0bd7580c645', + avatar: 'images/avatars/female-02.jpg', + name: 'Tina Harris', + email: 'tinaharris@mail.ca', + phone: '+1-933-464-2431', + title: 'Designer', + }, + { + id: 'f004ea79-98fc-436c-9ba5-6cfe32fe583d', + avatar: 'images/avatars/male-02.jpg', + name: 'Holt Manning', + email: 'holtmanning@mail.org', + phone: '+1-822-531-2600', + title: 'Marketing Manager', + }, + { + id: '8b69fe2d-d7cc-4a3d-983d-559173e37d37', + avatar: 'images/avatars/female-03.jpg', + name: 'Misty Ramsey', + email: 'mistyramsey@mail.us', + phone: '+1-990-457-2106', + title: 'Consultant', + }, + ], +}; diff --git a/src/app/mock-api/index.ts b/src/app/mock-api/index.ts new file mode 100644 index 0000000..cfe04a2 --- /dev/null +++ b/src/app/mock-api/index.ts @@ -0,0 +1,45 @@ +import { AcademyMockApi } from 'app/mock-api/apps/academy/api'; +import { ChatMockApi } from 'app/mock-api/apps/chat/api'; +import { ContactsMockApi } from 'app/mock-api/apps/contacts/api'; +import { ECommerceInventoryMockApi } from 'app/mock-api/apps/ecommerce/inventory/api'; +import { FileManagerMockApi } from 'app/mock-api/apps/file-manager/api'; +import { HelpCenterMockApi } from 'app/mock-api/apps/help-center/api'; +import { MailboxMockApi } from 'app/mock-api/apps/mailbox/api'; +import { NotesMockApi } from 'app/mock-api/apps/notes/api'; +import { ScrumboardMockApi } from 'app/mock-api/apps/scrumboard/api'; +import { TasksMockApi } from 'app/mock-api/apps/tasks/api'; +import { AuthMockApi } from 'app/mock-api/common/auth/api'; +import { NavigationMockApi } from 'app/mock-api/common/navigation/api'; +import { NotificationsMockApi } from 'app/mock-api/common/notifications/api'; +import { SearchMockApi } from 'app/mock-api/common/search/api'; +import { UserMockApi } from 'app/mock-api/common/user/api'; +import { AnalyticsMockApi } from 'app/mock-api/dashboards/analytics/api'; +import { CryptoMockApi } from 'app/mock-api/dashboards/crypto/api'; +import { FinanceMockApi } from 'app/mock-api/dashboards/finance/api'; +import { ProjectMockApi } from 'app/mock-api/dashboards/project/api'; +import { ActivitiesMockApi } from 'app/mock-api/pages/activities/api'; +import { IconsMockApi } from 'app/mock-api/ui/icons/api'; + +export const mockApiServices = [ + AcademyMockApi, + ActivitiesMockApi, + AnalyticsMockApi, + AuthMockApi, + ChatMockApi, + ContactsMockApi, + CryptoMockApi, + ECommerceInventoryMockApi, + FileManagerMockApi, + FinanceMockApi, + HelpCenterMockApi, + IconsMockApi, + MailboxMockApi, + NavigationMockApi, + NotesMockApi, + NotificationsMockApi, + ProjectMockApi, + SearchMockApi, + ScrumboardMockApi, + TasksMockApi, + UserMockApi, +]; diff --git a/src/app/mock-api/pages/activities/api.ts b/src/app/mock-api/pages/activities/api.ts new file mode 100644 index 0000000..e32c6ed --- /dev/null +++ b/src/app/mock-api/pages/activities/api.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { activities as activitiesData } from 'app/mock-api/pages/activities/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class ActivitiesMockApi { + private _activities: any = activitiesData; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Activities - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/pages/activities') + .reply(() => [200, cloneDeep(this._activities)]); + } +} diff --git a/src/app/mock-api/pages/activities/data.ts b/src/app/mock-api/pages/activities/data.ts new file mode 100644 index 0000000..8040efa --- /dev/null +++ b/src/app/mock-api/pages/activities/data.ts @@ -0,0 +1,95 @@ +/* eslint-disable */ +import { DateTime } from 'luxon'; + +/* Get the current instant */ +const now = DateTime.now(); + +export const activities = [ + { + id: '493190c9-5b61-4912-afe5-78c21f1044d7', + icon: 'heroicons_solid:star', + description: 'Your submission has been accepted', + date: now.minus({ minutes: 25 }).toISO(), // 25 minutes ago + extraContent: `
Congratulations for your acceptance!

+
Hi Brian,
Your submission has been accepted and you are ready to move into the next phase. Once you are ready, reach out to me and we will ...
`, + }, + { + id: '6e3e97e5-effc-4fb7-b730-52a151f0b641', + image: 'images/avatars/male-04.jpg', + description: + 'Leo Gill added you to Top Secret Project group and assigned you as a Project Manager', + date: now.minus({ minutes: 50 }).toISO(), // 50 minutes ago + linkedContent: 'Top Secret Project', + link: '/dashboards/project', + useRouter: true, + }, + { + id: 'b91ccb58-b06c-413b-b389-87010e03a120', + icon: 'heroicons_solid:envelope', + description: 'You have 15 unread mails across 3 mailboxes', + date: now.minus({ hours: 3 }).toISO(), // 3 hours ago + linkedContent: 'Mailbox', + link: '/apps/mailbox', + useRouter: true, + }, + { + id: '541416c9-84a7-408a-8d74-27a43c38d797', + icon: 'heroicons_solid:arrow-path', + description: + 'Your Docker container is ready to publish', + date: now.minus({ hours: 5 }).toISO(), // 5 hours ago + linkedContent: 'Download the container', + link: '.', + useRouter: true, + }, + { + id: 'ef7b95a7-8e8b-4616-9619-130d9533add9', + image: 'images/avatars/male-06.jpg', + description: + 'Roger Murray accepted your friend request', + date: now.minus({ hours: 7 }).toISO(), // 7 hours ago + extraContent: `You have 8 mutual friends.`, + }, + { + id: 'eb8aa470-635e-461d-88e1-23d9ea2a5665', + image: 'images/avatars/female-04.jpg', + description: 'Sophie Stone sent you a direct message', + date: now.minus({ hours: 9 }).toISO(), // 9 hours ago + }, + { + id: 'b85c2338-cc98-4140-bbf8-c226ce4e395e', + icon: 'heroicons_solid:envelope', + description: 'You have 3 new mails', + date: now.minus({ day: 1 }).toISO(), // 1 day ago + extraContent: `
    +
  1. Please review and sign the attached agreement
  2. +
  3. Delivery address confirmation
  4. +
  5. Previous clients and their invoices
  6. +
`, + linkedContent: 'Mailbox', + link: '/apps/mailbox', + useRouter: true, + }, + { + id: 'fd0f01b4-f3de-4333-add5-cd86850279f8', + image: 'images/avatars/female-02.jpg', + description: 'Tina Harris started a chat with you', + date: now.minus({ day: 1 }).toISO(), // 1 day ago, + linkedContent: 'Go to Chat (Tina Harris)', + link: '/apps/chat/5636c0ba-fa47-42ca-9160-27340583041e', + useRouter: true, + }, + { + id: '8f8e1bf9-4661-4939-9e43-390957b60f42', + icon: 'heroicons_solid:star', + description: + 'Your submission has been accepted and you are ready to sign-up for the final assigment which will be ready in 2 days', + date: now.minus({ days: 3 }).toISO(), // 3 days ago + }, + { + id: '30af917b-7a6a-45d1-822f-9e7ad7f8bf69', + icon: 'heroicons_solid:arrow-path', + description: 'Your Vagrant container is ready to download', + date: now.minus({ day: 4 }).toISO(), // 4 days ago + }, +]; diff --git a/src/app/mock-api/ui/icons/api.ts b/src/app/mock-api/ui/icons/api.ts new file mode 100644 index 0000000..348c00d --- /dev/null +++ b/src/app/mock-api/ui/icons/api.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@angular/core'; +import { AngorMockApiService } from '@angor/lib/mock-api'; +import { feather, heroicons, material } from 'app/mock-api/ui/icons/data'; +import { cloneDeep } from 'lodash-es'; + +@Injectable({ providedIn: 'root' }) +export class IconsMockApi { + private readonly _feather: any = feather; + private readonly _heroicons: any = heroicons; + private readonly _material: any = material; + + /** + * Constructor + */ + constructor(private _angorMockApiService: AngorMockApiService) { + // Register Mock API handlers + this.registerHandlers(); + } + + // ----------------------------------------------------------------------------------------------------- + // @ Public methods + // ----------------------------------------------------------------------------------------------------- + + /** + * Register Mock API handlers + */ + registerHandlers(): void { + // ----------------------------------------------------------------------------------------------------- + // @ Feather icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService.onGet('api/ui/icons/feather').reply(() => [ + 200, + { + namespace: 'feather', + name: 'Feather', + grid: 'icon-size-6', + list: cloneDeep(this._feather), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Heroicons outline icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/heroicons-outline') + .reply(() => [ + 200, + { + namespace: 'heroicons_outline', + name: 'Heroicons Outline', + grid: 'icon-size-6', + list: cloneDeep(this._heroicons), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Heroicons solid icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/heroicons-solid') + .reply(() => [ + 200, + { + namespace: 'heroicons_solid', + name: 'Heroicons Solid', + grid: 'icon-size-6', + list: cloneDeep(this._heroicons), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Heroicons mini icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/heroicons-mini') + .reply(() => [ + 200, + { + namespace: 'heroicons_mini', + name: 'Heroicons Mini', + grid: 'icon-size-5', + list: cloneDeep(this._heroicons), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Material solid icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/material-solid') + .reply(() => [ + 200, + { + namespace: 'mat_solid', + name: 'Material Solid', + grid: 'icon-size-6', + list: cloneDeep(this._material), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Material outline icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/material-outline') + .reply(() => [ + 200, + { + namespace: 'mat_outline', + name: 'Material Outline', + grid: 'icon-size-6', + list: cloneDeep(this._material), + }, + ]); + + // ----------------------------------------------------------------------------------------------------- + // @ Material twotone icons - GET + // ----------------------------------------------------------------------------------------------------- + this._angorMockApiService + .onGet('api/ui/icons/material-twotone') + .reply(() => [ + 200, + { + namespace: '', + name: 'Material Twotone', + grid: 'icon-size-6', + list: cloneDeep(this._material), + }, + ]); + } +} diff --git a/src/app/mock-api/ui/icons/data.ts b/src/app/mock-api/ui/icons/data.ts new file mode 100644 index 0000000..3fa2a72 --- /dev/null +++ b/src/app/mock-api/ui/icons/data.ts @@ -0,0 +1,2376 @@ +/* eslint-disable */ + +// Updated at: 20210425 - 1792 icons +export const material = [ + '10k', + '10mp', + '11mp', + '12mp', + '13mp', + '14mp', + '15mp', + '16mp', + '17mp', + '18mp', + '19mp', + '1k', + '1k_plus', + '1x_mobiledata', + '20mp', + '21mp', + '22mp', + '23mp', + '24mp', + '2k', + '2k_plus', + '2mp', + '30fps', + '30fps_select', + '360', + '3d_rotation', + '3g_mobiledata', + '3k', + '3k_plus', + '3mp', + '3p', + '4g_mobiledata', + '4g_plus_mobiledata', + '4k', + '4k_plus', + '4mp', + '5g', + '5k', + '5k_plus', + '5mp', + '60fps', + '60fps_select', + '6_ft_apart', + '6k', + '6k_plus', + '6mp', + '7k', + '7k_plus', + '7mp', + '8k', + '8k_plus', + '8mp', + '9k', + '9k_plus', + '9mp', + 'ac_unit', + 'access_alarm', + 'access_alarms', + 'access_time', + 'access_time_filled', + 'accessibility', + 'accessibility_new', + 'accessible', + 'accessible_forward', + 'account_balance', + 'account_balance_wallet', + 'account_box', + 'account_circle', + 'account_tree', + 'ad_units', + 'adb', + 'add', + 'add_a_photo', + 'add_alarm', + 'add_alert', + 'add_box', + 'add_business', + 'add_chart', + 'add_circle', + 'add_circle_outline', + 'add_comment', + 'add_ic_call', + 'add_link', + 'add_location', + 'add_location_alt', + 'add_moderator', + 'add_photo_alternate', + 'add_reaction', + 'add_road', + 'add_shopping_cart', + 'add_task', + 'add_to_drive', + 'add_to_home_screen', + 'add_to_photos', + 'add_to_queue', + 'addchart', + 'adjust', + 'admin_panel_settings', + 'ads_click', + 'agriculture', + 'air', + 'airline_seat_flat', + 'airline_seat_flat_angled', + 'airline_seat_individual_suite', + 'airline_seat_legroom_extra', + 'airline_seat_legroom_normal', + 'airline_seat_legroom_reduced', + 'airline_seat_recline_extra', + 'airline_seat_recline_normal', + 'airplane_ticket', + 'airplanemode_active', + 'airplanemode_inactive', + 'airplay', + 'airport_shuttle', + 'alarm', + 'alarm_add', + 'alarm_off', + 'alarm_on', + 'album', + 'align_horizontal_center', + 'align_horizontal_left', + 'align_horizontal_right', + 'align_vertical_bottom', + 'align_vertical_center', + 'align_vertical_top', + 'all_inbox', + 'all_inclusive', + 'all_out', + 'alt_route', + 'alternate_email', + 'analytics', + 'anchor', + 'android', + 'animation', + 'announcement', + 'aod', + 'apartment', + 'api', + 'app_blocking', + 'app_registration', + 'app_settings_alt', + 'approval', + 'apps', + 'architecture', + 'archive', + 'area_chart', + 'arrow_back', + 'arrow_back_ios', + 'arrow_back_ios_new', + 'arrow_circle_down', + 'arrow_circle_up', + 'arrow_downward', + 'arrow_drop_down', + 'arrow_drop_down_circle', + 'arrow_drop_up', + 'arrow_forward', + 'arrow_forward_ios', + 'arrow_left', + 'arrow_right', + 'arrow_right_alt', + 'arrow_upward', + 'art_track', + 'article', + 'aspect_ratio', + 'assessment', + 'assignment', + 'assignment_ind', + 'assignment_late', + 'assignment_return', + 'assignment_returned', + 'assignment_turned_in', + 'assistant', + 'assistant_direction', + 'assistant_photo', + 'atm', + 'attach_email', + 'attach_file', + 'attach_money', + 'attachment', + 'attractions', + 'attribution', + 'audiotrack', + 'auto_awesome', + 'auto_awesome_mosaic', + 'auto_awesome_motion', + 'auto_delete', + 'auto_fix_high', + 'auto_fix_normal', + 'auto_fix_off', + 'auto_graph', + 'auto_stories', + 'autofps_select', + 'autorenew', + 'av_timer', + 'baby_changing_station', + 'back_hand', + 'backpack', + 'backspace', + 'backup', + 'backup_table', + 'badge', + 'bakery_dining', + 'balcony', + 'ballot', + 'bar_chart', + 'batch_prediction', + 'bathroom', + 'bathtub', + 'battery_alert', + 'battery_charging_full', + 'battery_full', + 'battery_saver', + 'battery_std', + 'battery_unknown', + 'beach_access', + 'bed', + 'bedroom_baby', + 'bedroom_child', + 'bedroom_parent', + 'bedtime', + 'beenhere', + 'bento', + 'bike_scooter', + 'biotech', + 'blender', + 'block', + 'bloodtype', + 'bluetooth', + 'bluetooth_audio', + 'bluetooth_connected', + 'bluetooth_disabled', + 'bluetooth_drive', + 'bluetooth_searching', + 'blur_circular', + 'blur_linear', + 'blur_off', + 'blur_on', + 'bolt', + 'book', + 'book_online', + 'bookmark', + 'bookmark_add', + 'bookmark_added', + 'bookmark_border', + 'bookmark_remove', + 'bookmarks', + 'border_all', + 'border_bottom', + 'border_clear', + 'border_color', + 'border_horizontal', + 'border_inner', + 'border_left', + 'border_outer', + 'border_right', + 'border_style', + 'border_top', + 'border_vertical', + 'branding_watermark', + 'breakfast_dining', + 'brightness_1', + 'brightness_2', + 'brightness_3', + 'brightness_4', + 'brightness_5', + 'brightness_6', + 'brightness_7', + 'brightness_auto', + 'brightness_high', + 'brightness_low', + 'brightness_medium', + 'broken_image', + 'browser_not_supported', + 'brunch_dining', + 'brush', + 'bubble_chart', + 'bug_report', + 'build', + 'build_circle', + 'bungalow', + 'burst_mode', + 'bus_alert', + 'business', + 'business_center', + 'cabin', + 'cable', + 'cached', + 'cake', + 'calculate', + 'calendar_today', + 'calendar_view_day', + 'calendar_view_month', + 'calendar_view_week', + 'call', + 'call_end', + 'call_made', + 'call_merge', + 'call_missed', + 'call_missed_outgoing', + 'call_received', + 'call_split', + 'call_to_action', + 'camera', + 'camera_alt', + 'camera_enhance', + 'camera_front', + 'camera_indoor', + 'camera_outdoor', + 'camera_rear', + 'camera_roll', + 'cameraswitch', + 'campaign', + 'cancel', + 'cancel_presentation', + 'cancel_schedule_send', + 'car_rental', + 'car_repair', + 'card_giftcard', + 'card_membership', + 'card_travel', + 'carpenter', + 'cases', + 'casino', + 'cast', + 'cast_connected', + 'cast_for_education', + 'catching_pokemon', + 'category', + 'celebration', + 'cell_wifi', + 'center_focus_strong', + 'center_focus_weak', + 'chair', + 'chair_alt', + 'chalet', + 'change_circle', + 'change_history', + 'charging_station', + 'chat', + 'chat_bubble', + 'chat_bubble_outline', + 'check', + 'check_box', + 'check_box_outline_blank', + 'check_circle', + 'check_circle_outline', + 'checklist', + 'checklist_rtl', + 'checkroom', + 'chevron_left', + 'chevron_right', + 'child_care', + 'child_friendly', + 'chrome_reader_mode', + 'circle', + 'circle_notifications', + 'class', + 'clean_hands', + 'cleaning_services', + 'clear', + 'clear_all', + 'close', + 'close_fullscreen', + 'closed_caption', + 'closed_caption_disabled', + 'closed_caption_off', + 'cloud', + 'cloud_circle', + 'cloud_done', + 'cloud_download', + 'cloud_off', + 'cloud_queue', + 'cloud_upload', + 'code', + 'code_off', + 'coffee', + 'coffee_maker', + 'collections', + 'collections_bookmark', + 'color_lens', + 'colorize', + 'comment', + 'comment_bank', + 'commute', + 'compare', + 'compare_arrows', + 'compass_calibration', + 'compost', + 'compress', + 'computer', + 'confirmation_number', + 'connect_without_contact', + 'connected_tv', + 'construction', + 'contact_mail', + 'contact_page', + 'contact_phone', + 'contact_support', + 'contactless', + 'contacts', + 'content_copy', + 'content_cut', + 'content_paste', + 'content_paste_off', + 'control_camera', + 'control_point', + 'control_point_duplicate', + 'copy_all', + 'copyright', + 'coronavirus', + 'corporate_fare', + 'cottage', + 'countertops', + 'create', + 'create_new_folder', + 'credit_card', + 'credit_card_off', + 'credit_score', + 'crib', + 'crop', + 'crop_16_9', + 'crop_3_2', + 'crop_5_4', + 'crop_7_5', + 'crop_din', + 'crop_free', + 'crop_landscape', + 'crop_original', + 'crop_portrait', + 'crop_rotate', + 'crop_square', + 'cruelty_free', + 'dangerous', + 'dark_mode', + 'dashboard', + 'dashboard_customize', + 'data_exploration', + 'data_saver_off', + 'data_saver_on', + 'data_usage', + 'date_range', + 'deck', + 'dehaze', + 'delete', + 'delete_forever', + 'delete_outline', + 'delete_sweep', + 'delivery_dining', + 'departure_board', + 'description', + 'design_services', + 'desktop_access_disabled', + 'desktop_mac', + 'desktop_windows', + 'details', + 'developer_board', + 'developer_board_off', + 'developer_mode', + 'device_hub', + 'device_thermostat', + 'device_unknown', + 'devices', + 'devices_other', + 'dialer_sip', + 'dialpad', + 'dining', + 'dinner_dining', + 'directions', + 'directions_bike', + 'directions_boat', + 'directions_boat_filled', + 'directions_bus', + 'directions_bus_filled', + 'directions_car', + 'directions_car_filled', + 'directions_off', + 'directions_railway', + 'directions_railway_filled', + 'directions_run', + 'directions_subway', + 'directions_subway_filled', + 'directions_transit', + 'directions_transit_filled', + 'directions_walk', + 'dirty_lens', + 'disabled_by_default', + 'disabled_visible', + 'disc_full', + 'dns', + 'do_disturb', + 'do_disturb_alt', + 'do_disturb_off', + 'do_disturb_on', + 'do_not_disturb', + 'do_not_disturb_alt', + 'do_not_disturb_off', + 'do_not_disturb_on', + 'do_not_disturb_on_total_silence', + 'do_not_step', + 'do_not_touch', + 'dock', + 'document_scanner', + 'domain', + 'domain_disabled', + 'domain_verification', + 'done', + 'done_all', + 'done_outline', + 'donut_large', + 'donut_small', + 'door_back', + 'door_front', + 'door_sliding', + 'doorbell', + 'double_arrow', + 'downhill_skiing', + 'download', + 'download_done', + 'download_for_offline', + 'downloading', + 'drafts', + 'drag_handle', + 'drag_indicator', + 'draw', + 'drive_eta', + 'drive_file_move', + 'drive_file_move_rtl', + 'drive_file_rename_outline', + 'drive_folder_upload', + 'dry', + 'dry_cleaning', + 'duo', + 'dvr', + 'dynamic_feed', + 'dynamic_form', + 'e_mobiledata', + 'earbuds', + 'earbuds_battery', + 'east', + 'edgesensor_high', + 'edgesensor_low', + 'edit', + 'edit_attributes', + 'edit_calendar', + 'edit_location', + 'edit_location_alt', + 'edit_note', + 'edit_notifications', + 'edit_off', + 'edit_road', + 'eject', + 'elderly', + 'electric_bike', + 'electric_car', + 'electric_moped', + 'electric_rickshaw', + 'electric_scooter', + 'electrical_services', + 'elevator', + 'email', + 'emergency', + 'emoji_emotions', + 'emoji_events', + 'emoji_flags', + 'emoji_food_beverage', + 'emoji_nature', + 'emoji_objects', + 'emoji_people', + 'emoji_symbols', + 'emoji_transportation', + 'engineering', + 'enhanced_encryption', + 'equalizer', + 'error', + 'error_outline', + 'escalator', + 'escalator_warning', + 'euro', + 'euro_symbol', + 'ev_station', + 'event', + 'event_available', + 'event_busy', + 'event_note', + 'event_seat', + 'exit_to_app', + 'expand', + 'expand_less', + 'expand_more', + 'explicit', + 'explore', + 'explore_off', + 'exposure', + 'exposure_neg_1', + 'exposure_neg_2', + 'exposure_plus_1', + 'exposure_plus_2', + 'exposure_zero', + 'extension', + 'extension_off', + 'face', + 'face_retouching_natural', + 'face_retouching_off', + 'facebook', + 'fact_check', + 'family_restroom', + 'fast_forward', + 'fast_rewind', + 'fastfood', + 'favorite', + 'favorite_border', + 'featured_play_list', + 'featured_video', + 'feed', + 'feedback', + 'female', + 'fence', + 'festival', + 'fiber_dvr', + 'fiber_manual_record', + 'fiber_new', + 'fiber_pin', + 'fiber_smart_record', + 'file_copy', + 'file_download', + 'file_download_done', + 'file_download_off', + 'file_present', + 'file_upload', + 'filter', + 'filter_1', + 'filter_2', + 'filter_3', + 'filter_4', + 'filter_5', + 'filter_6', + 'filter_7', + 'filter_8', + 'filter_9', + 'filter_9_plus', + 'filter_alt', + 'filter_b_and_w', + 'filter_center_focus', + 'filter_drama', + 'filter_frames', + 'filter_hdr', + 'filter_list', + 'filter_none', + 'filter_tilt_shift', + 'filter_vintage', + 'find_in_page', + 'find_replace', + 'fingerprint', + 'fire_extinguisher', + 'fireplace', + 'first_page', + 'fit_screen', + 'fitness_center', + 'flag', + 'flaky', + 'flare', + 'flash_auto', + 'flash_off', + 'flash_on', + 'flashlight_off', + 'flashlight_on', + 'flatware', + 'flight', + 'flight_land', + 'flight_takeoff', + 'flip', + 'flip_camera_android', + 'flip_camera_ios', + 'flip_to_back', + 'flip_to_front', + 'flourescent', + 'flutter_dash', + 'fmd_bad', + 'fmd_good', + 'folder', + 'folder_open', + 'folder_shared', + 'folder_special', + 'follow_the_signs', + 'font_download', + 'font_download_off', + 'food_bank', + 'format_align_center', + 'format_align_justify', + 'format_align_left', + 'format_align_right', + 'format_bold', + 'format_clear', + 'format_color_fill', + 'format_color_reset', + 'format_color_text', + 'format_indent_decrease', + 'format_indent_increase', + 'format_italic', + 'format_line_spacing', + 'format_list_bulleted', + 'format_list_numbered', + 'format_list_numbered_rtl', + 'format_paint', + 'format_quote', + 'format_shapes', + 'format_size', + 'format_strikethrough', + 'format_textdirection_l_to_r', + 'format_textdirection_r_to_l', + 'format_underlined', + 'forum', + 'forward', + 'forward_10', + 'forward_30', + 'forward_5', + 'forward_to_inbox', + 'foundation', + 'free_breakfast', + 'free_cancellation', + 'front_hand', + 'fullscreen', + 'fullscreen_exit', + 'functions', + 'g_mobiledata', + 'g_translate', + 'gamepad', + 'games', + 'garage', + 'gavel', + 'generating_tokens', + 'gesture', + 'get_app', + 'gif', + 'gite', + 'golf_course', + 'gpp_bad', + 'gpp_good', + 'gpp_maybe', + 'gps_fixed', + 'gps_not_fixed', + 'gps_off', + 'grade', + 'gradient', + 'grading', + 'grain', + 'graphic_eq', + 'grass', + 'grid_3x3', + 'grid_4x4', + 'grid_goldenratio', + 'grid_off', + 'grid_on', + 'grid_view', + 'group', + 'group_add', + 'group_off', + 'group_work', + 'groups', + 'h_mobiledata', + 'h_plus_mobiledata', + 'hail', + 'handyman', + 'hardware', + 'hd', + 'hdr_auto', + 'hdr_auto_select', + 'hdr_enhanced_select', + 'hdr_off', + 'hdr_off_select', + 'hdr_on', + 'hdr_on_select', + 'hdr_plus', + 'hdr_strong', + 'hdr_weak', + 'headphones', + 'headphones_battery', + 'headset', + 'headset_mic', + 'headset_off', + 'healing', + 'health_and_safety', + 'hearing', + 'hearing_disabled', + 'height', + 'help', + 'help_center', + 'help_outline', + 'hevc', + 'hide_image', + 'hide_source', + 'high_quality', + 'highlight', + 'highlight_alt', + 'highlight_off', + 'hiking', + 'history', + 'history_edu', + 'history_toggle_off', + 'holiday_village', + 'home', + 'home_max', + 'home_mini', + 'home_repair_service', + 'home_work', + 'horizontal_distribute', + 'horizontal_rule', + 'horizontal_split', + 'hot_tub', + 'hotel', + 'hotel_class', + 'hourglass_bottom', + 'hourglass_disabled', + 'hourglass_empty', + 'hourglass_full', + 'hourglass_top', + 'house', + 'house_siding', + 'houseboat', + 'how_to_reg', + 'how_to_vote', + 'http', + 'https', + 'hvac', + 'ice_skating', + 'icecream', + 'image', + 'image_aspect_ratio', + 'image_not_supported', + 'image_search', + 'imagesearch_roller', + 'import_contacts', + 'import_export', + 'important_devices', + 'inbox', + 'incomplete_circle', + 'indeterminate_check_box', + 'info', + 'input', + 'insert_chart', + 'insert_chart_outlined', + 'insert_comment', + 'insert_drive_file', + 'insert_emoticon', + 'insert_invitation', + 'insert_link', + 'insert_photo', + 'insights', + 'integration_instructions', + 'inventory', + 'inventory_2', + 'invert_colors', + 'invert_colors_off', + 'ios_share', + 'iron', + 'iso', + 'kayaking', + 'keyboard', + 'keyboard_alt', + 'keyboard_arrow_down', + 'keyboard_arrow_left', + 'keyboard_arrow_right', + 'keyboard_arrow_up', + 'keyboard_backspace', + 'keyboard_capslock', + 'keyboard_hide', + 'keyboard_return', + 'keyboard_tab', + 'keyboard_voice', + 'king_bed', + 'kitchen', + 'kitesurfing', + 'label', + 'label_important', + 'label_off', + 'landscape', + 'language', + 'laptop', + 'laptop_chromebook', + 'laptop_mac', + 'laptop_windows', + 'last_page', + 'launch', + 'layers', + 'layers_clear', + 'leaderboard', + 'leak_add', + 'leak_remove', + 'legend_toggle', + 'lens', + 'lens_blur', + 'library_add', + 'library_add_check', + 'library_books', + 'library_music', + 'light', + 'light_mode', + 'lightbulb', + 'line_style', + 'line_weight', + 'linear_scale', + 'link', + 'link_off', + 'linked_camera', + 'liquor', + 'list', + 'list_alt', + 'live_help', + 'live_tv', + 'living', + 'local_activity', + 'local_airport', + 'local_atm', + 'local_bar', + 'local_cafe', + 'local_car_wash', + 'local_convenience_store', + 'local_dining', + 'local_drink', + 'local_fire_department', + 'local_florist', + 'local_gas_station', + 'local_grocery_store', + 'local_hospital', + 'local_hotel', + 'local_laundry_service', + 'local_library', + 'local_mall', + 'local_movies', + 'local_offer', + 'local_parking', + 'local_pharmacy', + 'local_phone', + 'local_pizza', + 'local_play', + 'local_police', + 'local_post_office', + 'local_printshop', + 'local_see', + 'local_shipping', + 'local_taxi', + 'location_city', + 'location_disabled', + 'location_off', + 'location_on', + 'location_searching', + 'lock', + 'lock_clock', + 'lock_open', + 'login', + 'logout', + 'looks', + 'looks_3', + 'looks_4', + 'looks_5', + 'looks_6', + 'looks_one', + 'looks_two', + 'loop', + 'loupe', + 'low_priority', + 'loyalty', + 'lte_mobiledata', + 'lte_plus_mobiledata', + 'luggage', + 'lunch_dining', + 'mail', + 'mail_outline', + 'male', + 'manage_accounts', + 'manage_search', + 'map', + 'maps_home_work', + 'maps_ugc', + 'margin', + 'mark_as_unread', + 'mark_chat_read', + 'mark_chat_unread', + 'mark_email_read', + 'mark_email_unread', + 'markunread', + 'markunread_mailbox', + 'masks', + 'maximize', + 'media_bluetooth_off', + 'media_bluetooth_on', + 'mediation', + 'medical_services', + 'medication', + 'meeting_room', + 'memory', + 'menu', + 'menu_book', + 'menu_open', + 'merge_type', + 'message', + 'mic', + 'mic_external_off', + 'mic_external_on', + 'mic_none', + 'mic_off', + 'microwave', + 'military_tech', + 'minimize', + 'miscellaneous_services', + 'missed_video_call', + 'mms', + 'mobile_friendly', + 'mobile_off', + 'mobile_screen_share', + 'mobiledata_off', + 'mode', + 'mode_comment', + 'mode_edit', + 'mode_edit_outline', + 'mode_night', + 'mode_standby', + 'model_training', + 'monetization_on', + 'money', + 'money_off', + 'money_off_csred', + 'monitor', + 'monitor_weight', + 'monochrome_photos', + 'mood', + 'mood_bad', + 'moped', + 'more', + 'more_horiz', + 'more_time', + 'more_vert', + 'motion_photos_auto', + 'motion_photos_off', + 'motion_photos_on', + 'motion_photos_pause', + 'motion_photos_paused', + 'mouse', + 'move_to_inbox', + 'movie', + 'movie_creation', + 'movie_filter', + 'moving', + 'mp', + 'multiline_chart', + 'multiple_stop', + 'museum', + 'music_note', + 'music_off', + 'music_video', + 'my_location', + 'nat', + 'nature', + 'nature_people', + 'navigate_before', + 'navigate_next', + 'navigation', + 'near_me', + 'near_me_disabled', + 'nearby_error', + 'nearby_off', + 'network_cell', + 'network_check', + 'network_locked', + 'network_wifi', + 'new_label', + 'new_releases', + 'next_plan', + 'next_week', + 'nfc', + 'night_shelter', + 'nightlife', + 'nightlight', + 'nightlight_round', + 'nights_stay', + 'no_accounts', + 'no_backpack', + 'no_cell', + 'no_drinks', + 'no_encryption', + 'no_encryption_gmailerrorred', + 'no_flash', + 'no_food', + 'no_luggage', + 'no_meals', + 'no_meeting_room', + 'no_photography', + 'no_sim', + 'no_stroller', + 'no_transfer', + 'nordic_walking', + 'north', + 'north_east', + 'north_west', + 'not_accessible', + 'not_interested', + 'not_listed_location', + 'not_started', + 'note', + 'note_add', + 'note_alt', + 'notes', + 'notification_add', + 'notification_important', + 'notifications', + 'notifications_active', + 'notifications_none', + 'notifications_off', + 'notifications_paused', + 'offline_bolt', + 'offline_pin', + 'offline_share', + 'ondemand_video', + 'online_prediction', + 'opacity', + 'open_in_browser', + 'open_in_full', + 'open_in_new', + 'open_in_new_off', + 'open_with', + 'other_houses', + 'outbound', + 'outbox', + 'outdoor_grill', + 'outlet', + 'outlined_flag', + 'padding', + 'pages', + 'pageview', + 'paid', + 'palette', + 'pan_tool', + 'panorama', + 'panorama_fish_eye', + 'panorama_horizontal', + 'panorama_horizontal_select', + 'panorama_photosphere', + 'panorama_photosphere_select', + 'panorama_vertical', + 'panorama_vertical_select', + 'panorama_wide_angle', + 'panorama_wide_angle_select', + 'paragliding', + 'park', + 'party_mode', + 'password', + 'pattern', + 'pause', + 'pause_circle', + 'pause_circle_filled', + 'pause_circle_outline', + 'pause_presentation', + 'payment', + 'payments', + 'pedal_bike', + 'pending', + 'pending_actions', + 'people', + 'people_alt', + 'people_outline', + 'perm_camera_mic', + 'perm_contact_calendar', + 'perm_data_setting', + 'perm_device_information', + 'perm_identity', + 'perm_media', + 'perm_phone_msg', + 'perm_scan_wifi', + 'person', + 'person_add', + 'person_add_alt', + 'person_add_alt_1', + 'person_add_disabled', + 'person_off', + 'person_outline', + 'person_pin', + 'person_pin_circle', + 'person_remove', + 'person_remove_alt_1', + 'person_search', + 'personal_injury', + 'personal_video', + 'pest_control', + 'pest_control_rodent', + 'pets', + 'phone', + 'phone_android', + 'phone_bluetooth_speaker', + 'phone_callback', + 'phone_disabled', + 'phone_enabled', + 'phone_forwarded', + 'phone_in_talk', + 'phone_iphone', + 'phone_locked', + 'phone_missed', + 'phone_paused', + 'phonelink', + 'phonelink_erase', + 'phonelink_lock', + 'phonelink_off', + 'phonelink_ring', + 'phonelink_setup', + 'photo', + 'photo_album', + 'photo_camera', + 'photo_camera_back', + 'photo_camera_front', + 'photo_filter', + 'photo_library', + 'photo_size_select_actual', + 'photo_size_select_large', + 'photo_size_select_small', + 'piano', + 'piano_off', + 'picture_as_pdf', + 'picture_in_picture', + 'picture_in_picture_alt', + 'pie_chart', + 'pie_chart_outline', + 'pin', + 'pin_drop', + 'pin_end', + 'pin_invoke', + 'pivot_table_chart', + 'place', + 'plagiarism', + 'play_arrow', + 'play_circle', + 'play_circle_filled', + 'play_circle_outline', + 'play_disabled', + 'play_for_work', + 'play_lesson', + 'playlist_add', + 'playlist_add_check', + 'playlist_play', + 'plumbing', + 'plus_one', + 'podcasts', + 'point_of_sale', + 'policy', + 'poll', + 'polymer', + 'pool', + 'portable_wifi_off', + 'portrait', + 'post_add', + 'power', + 'power_input', + 'power_off', + 'power_settings_new', + 'precision_manufacturing', + 'pregnant_woman', + 'present_to_all', + 'preview', + 'price_change', + 'price_check', + 'print', + 'print_disabled', + 'priority_high', + 'privacy_tip', + 'private_connectivity', + 'production_quantity_limits', + 'psychology', + 'public', + 'public_off', + 'publish', + 'published_with_changes', + 'push_pin', + 'qr_code', + 'qr_code_2', + 'qr_code_scanner', + 'query_builder', + 'query_stats', + 'question_answer', + 'queue', + 'queue_music', + 'queue_play_next', + 'quickreply', + 'quiz', + 'r_mobiledata', + 'radar', + 'radio', + 'radio_button_checked', + 'radio_button_unchecked', + 'railway_alert', + 'ramen_dining', + 'rate_review', + 'raw_off', + 'raw_on', + 'read_more', + 'real_estate_agent', + 'receipt', + 'receipt_long', + 'recent_actors', + 'recommend', + 'record_voice_over', + 'recycling', + 'redeem', + 'redo', + 'reduce_capacity', + 'refresh', + 'remember_me', + 'remove', + 'remove_circle', + 'remove_circle_outline', + 'remove_done', + 'remove_from_queue', + 'remove_moderator', + 'remove_red_eye', + 'remove_shopping_cart', + 'reorder', + 'repeat', + 'repeat_on', + 'repeat_one', + 'repeat_one_on', + 'replay', + 'replay_10', + 'replay_30', + 'replay_5', + 'replay_circle_filled', + 'reply', + 'reply_all', + 'report', + 'report_gmailerrorred', + 'report_off', + 'report_problem', + 'request_page', + 'request_quote', + 'reset_tv', + 'restart_alt', + 'restaurant', + 'restaurant_menu', + 'restore', + 'restore_from_trash', + 'restore_page', + 'reviews', + 'rice_bowl', + 'ring_volume', + 'roofing', + 'room', + 'room_preferences', + 'room_service', + 'rotate_90_degrees_ccw', + 'rotate_left', + 'rotate_right', + 'rounded_corner', + 'router', + 'rowing', + 'rss_feed', + 'rsvp', + 'rtt', + 'rule', + 'rule_folder', + 'run_circle', + 'running_with_errors', + 'rv_hookup', + 'safety_divider', + 'sailing', + 'sanitizer', + 'satellite', + 'save', + 'save_alt', + 'saved_search', + 'savings', + 'scanner', + 'scatter_plot', + 'schedule', + 'schedule_send', + 'schema', + 'school', + 'science', + 'score', + 'screen_lock_landscape', + 'screen_lock_portrait', + 'screen_lock_rotation', + 'screen_rotation', + 'screen_search_desktop', + 'screen_share', + 'screenshot', + 'sd', + 'sd_card', + 'sd_card_alert', + 'sd_storage', + 'search', + 'search_off', + 'security', + 'security_update', + 'security_update_good', + 'security_update_warning', + 'segment', + 'select_all', + 'self_improvement', + 'sell', + 'send', + 'send_and_archive', + 'send_to_mobile', + 'sensor_door', + 'sensor_window', + 'sensors', + 'sensors_off', + 'sentiment_dissatisfied', + 'sentiment_neutral', + 'sentiment_satisfied', + 'sentiment_satisfied_alt', + 'sentiment_very_dissatisfied', + 'sentiment_very_satisfied', + 'set_meal', + 'settings', + 'settings_accessibility', + 'settings_applications', + 'settings_backup_restore', + 'settings_bluetooth', + 'settings_brightness', + 'settings_cell', + 'settings_ethernet', + 'settings_input_antenna', + 'settings_input_component', + 'settings_input_composite', + 'settings_input_hdmi', + 'settings_input_svideo', + 'settings_overscan', + 'settings_phone', + 'settings_power', + 'settings_remote', + 'settings_suggest', + 'settings_system_daydream', + 'settings_voice', + 'share', + 'share_location', + 'shield', + 'shop', + 'shop_2', + 'shop_two', + 'shopping_bag', + 'shopping_basket', + 'shopping_cart', + 'short_text', + 'shortcut', + 'show_chart', + 'shower', + 'shuffle', + 'shuffle_on', + 'shutter_speed', + 'sick', + 'signal_cellular_0_bar', + 'signal_cellular_4_bar', + 'signal_cellular_alt', + 'signal_cellular_connected_no_internet_0_bar', + 'signal_cellular_connected_no_internet_4_bar', + 'signal_cellular_no_sim', + 'signal_cellular_nodata', + 'signal_cellular_null', + 'signal_cellular_off', + 'signal_wifi_0_bar', + 'signal_wifi_4_bar', + 'signal_wifi_4_bar_lock', + 'signal_wifi_bad', + 'signal_wifi_connected_no_internet_4', + 'signal_wifi_off', + 'signal_wifi_statusbar_4_bar', + 'signal_wifi_statusbar_connected_no_internet_4', + 'signal_wifi_statusbar_null', + 'sim_card', + 'sim_card_alert', + 'sim_card_download', + 'single_bed', + 'sip', + 'skateboarding', + 'skip_next', + 'skip_previous', + 'sledding', + 'slideshow', + 'slow_motion_video', + 'smart_button', + 'smart_display', + 'smart_screen', + 'smart_toy', + 'smartphone', + 'smoke_free', + 'smoking_rooms', + 'sms', + 'sms_failed', + 'snippet_folder', + 'snooze', + 'snowboarding', + 'snowmobile', + 'snowshoeing', + 'soap', + 'social_distance', + 'sort', + 'sort_by_alpha', + 'source', + 'south', + 'south_east', + 'south_west', + 'spa', + 'space_bar', + 'space_dashboard', + 'speaker', + 'speaker_group', + 'speaker_notes', + 'speaker_notes_off', + 'speaker_phone', + 'speed', + 'spellcheck', + 'splitscreen', + 'sports', + 'sports_bar', + 'sports_baseball', + 'sports_basketball', + 'sports_cricket', + 'sports_esports', + 'sports_football', + 'sports_golf', + 'sports_handball', + 'sports_hockey', + 'sports_kabaddi', + 'sports_mma', + 'sports_motorsports', + 'sports_rugby', + 'sports_score', + 'sports_soccer', + 'sports_tennis', + 'sports_volleyball', + 'square_foot', + 'stacked_bar_chart', + 'stacked_line_chart', + 'stairs', + 'star', + 'star_border', + 'star_border_purple500', + 'star_half', + 'star_outline', + 'star_purple500', + 'star_rate', + 'stars', + 'stay_current_landscape', + 'stay_current_portrait', + 'stay_primary_landscape', + 'stay_primary_portrait', + 'sticky_note_2', + 'stop', + 'stop_circle', + 'stop_screen_share', + 'storage', + 'store', + 'store_mall_directory', + 'storefront', + 'storm', + 'straighten', + 'stream', + 'streetview', + 'strikethrough_s', + 'stroller', + 'style', + 'subdirectory_arrow_left', + 'subdirectory_arrow_right', + 'subject', + 'subscript', + 'subscriptions', + 'subtitles', + 'subtitles_off', + 'subway', + 'summarize', + 'superscript', + 'supervised_user_circle', + 'supervisor_account', + 'support', + 'support_agent', + 'surfing', + 'surround_sound', + 'swap_calls', + 'swap_horiz', + 'swap_horizontal_circle', + 'swap_vert', + 'swap_vertical_circle', + 'swipe', + 'switch_account', + 'switch_camera', + 'switch_left', + 'switch_right', + 'switch_video', + 'sync', + 'sync_alt', + 'sync_disabled', + 'sync_problem', + 'system_security_update', + 'system_security_update_good', + 'system_security_update_warning', + 'system_update', + 'system_update_alt', + 'tab', + 'tab_unselected', + 'table_chart', + 'table_rows', + 'table_view', + 'tablet', + 'tablet_android', + 'tablet_mac', + 'tag', + 'tag_faces', + 'takeout_dining', + 'tap_and_play', + 'tapas', + 'task', + 'task_alt', + 'taxi_alert', + 'terrain', + 'text_fields', + 'text_format', + 'text_rotate_up', + 'text_rotate_vertical', + 'text_rotation_angledown', + 'text_rotation_angleup', + 'text_rotation_down', + 'text_rotation_none', + 'text_snippet', + 'textsms', + 'texture', + 'theater_comedy', + 'theaters', + 'thermostat', + 'thermostat_auto', + 'thumb_down', + 'thumb_down_alt', + 'thumb_down_off_alt', + 'thumb_up', + 'thumb_up_alt', + 'thumb_up_off_alt', + 'thumbs_up_down', + 'time_to_leave', + 'timelapse', + 'timeline', + 'timer', + 'timer_10', + 'timer_10_select', + 'timer_3', + 'timer_3_select', + 'timer_off', + 'tips_and_updates', + 'title', + 'toc', + 'today', + 'toggle_off', + 'toggle_on', + 'toll', + 'tonality', + 'topic', + 'touch_app', + 'tour', + 'toys', + 'track_changes', + 'traffic', + 'train', + 'tram', + 'transfer_within_a_station', + 'transform', + 'transgender', + 'transit_enterexit', + 'translate', + 'travel_explore', + 'trending_down', + 'trending_flat', + 'trending_up', + 'trip_origin', + 'try', + 'tty', + 'tune', + 'tungsten', + 'turned_in', + 'turned_in_not', + 'tv', + 'tv_off', + 'two_wheeler', + 'umbrella', + 'unarchive', + 'undo', + 'unfold_less', + 'unfold_more', + 'unpublished', + 'unsubscribe', + 'upcoming', + 'update', + 'update_disabled', + 'upgrade', + 'upload', + 'upload_file', + 'usb', + 'usb_off', + 'verified', + 'verified_user', + 'vertical_align_bottom', + 'vertical_align_center', + 'vertical_align_top', + 'vertical_distribute', + 'vertical_split', + 'vibration', + 'video_call', + 'video_camera_back', + 'video_camera_front', + 'video_label', + 'video_library', + 'video_settings', + 'video_stable', + 'videocam', + 'videocam_off', + 'videogame_asset', + 'videogame_asset_off', + 'view_agenda', + 'view_array', + 'view_carousel', + 'view_column', + 'view_comfy', + 'view_compact', + 'view_day', + 'view_headline', + 'view_in_ar', + 'view_list', + 'view_module', + 'view_quilt', + 'view_sidebar', + 'view_stream', + 'view_week', + 'vignette', + 'villa', + 'visibility', + 'visibility_off', + 'voice_chat', + 'voice_over_off', + 'voicemail', + 'volume_down', + 'volume_mute', + 'volume_off', + 'volume_up', + 'volunteer_activism', + 'vpn_key', + 'vpn_lock', + 'vrpano', + 'wallpaper', + 'warning', + 'warning_amber', + 'wash', + 'watch', + 'watch_later', + 'water', + 'water_damage', + 'water_drop', + 'waterfall_chart', + 'waves', + 'waving_hand', + 'wb_auto', + 'wb_cloudy', + 'wb_incandescent', + 'wb_iridescent', + 'wb_shade', + 'wb_sunny', + 'wb_twilight', + 'wc', + 'web', + 'web_asset', + 'web_asset_off', + 'weekend', + 'west', + 'whatshot', + 'wheelchair_pickup', + 'where_to_vote', + 'widgets', + 'wifi', + 'wifi_calling', + 'wifi_calling_3', + 'wifi_lock', + 'wifi_off', + 'wifi_protected_setup', + 'wifi_tethering', + 'wifi_tethering_error_rounded', + 'wifi_tethering_off', + 'window', + 'wine_bar', + 'work', + 'work_off', + 'work_outline', + 'workspaces', + 'wrap_text', + 'wrong_location', + 'wysiwyg', + 'yard', + 'youtube_searched_for', + 'zoom_in', + 'zoom_out', + 'zoom_out_map', +]; +export const feather = [ + 'activity', + 'airplay', + 'alert-circle', + 'alert-octagon', + 'alert-triangle', + 'align-center', + 'align-justify', + 'align-left', + 'align-right', + 'anchor', + 'aperture', + 'archive', + 'arrow-down-circle', + 'arrow-down-left', + 'arrow-down-right', + 'arrow-down', + 'arrow-left-circle', + 'arrow-left', + 'arrow-right-circle', + 'arrow-right', + 'arrow-up-circle', + 'arrow-up-left', + 'arrow-up-right', + 'arrow-up', + 'at-sign', + 'award', + 'bar-chart-2', + 'bar-chart', + 'battery-charging', + 'battery', + 'bell-off', + 'bell', + 'bluetooth', + 'bold', + 'book-open', + 'book', + 'bookmark', + 'box', + 'briefcase', + 'calendar', + 'camera-off', + 'camera', + 'cast', + 'check-circle', + 'check-square', + 'check', + 'chevron-down', + 'chevron-left', + 'chevron-right', + 'chevron-up', + 'chevrons-down', + 'chevrons-left', + 'chevrons-right', + 'chevrons-up', + 'chrome', + 'circle', + 'clipboard', + 'clock', + 'cloud-drizzle', + 'cloud-lightning', + 'cloud-off', + 'cloud-rain', + 'cloud-snow', + 'cloud', + 'code', + 'codepen', + 'codesandbox', + 'coffee', + 'columns', + 'command', + 'compass', + 'copy', + 'corner-down-left', + 'corner-down-right', + 'corner-left-down', + 'corner-left-up', + 'corner-right-down', + 'corner-right-up', + 'corner-up-left', + 'corner-up-right', + 'cpu', + 'credit-card', + 'crop', + 'crosshair', + 'database', + 'delete', + 'disc', + 'dollar-sign', + 'download-cloud', + 'download', + 'droplet', + 'edit-2', + 'edit-3', + 'edit', + 'external-link', + 'eye-off', + 'eye', + 'facebook', + 'fast-forward', + 'feather', + 'figma', + 'file-minus', + 'file-plus', + 'file-text', + 'file', + 'film', + 'filter', + 'flag', + 'folder-minus', + 'folder-plus', + 'folder', + 'framer', + 'frown', + 'gift', + 'git-branch', + 'git-commit', + 'git-merge', + 'git-pull-request', + 'github', + 'gitlab', + 'globe', + 'grid', + 'hard-drive', + 'hash', + 'headphones', + 'heart', + 'help-circle', + 'hexagon', + 'home', + 'image', + 'inbox', + 'info', + 'instagram', + 'italic', + 'key', + 'layers', + 'layout', + 'life-buoy', + 'link-2', + 'link', + 'linkedin', + 'list', + 'loader', + 'lock', + 'log-in', + 'log-out', + 'mail', + 'map-pin', + 'map', + 'maximize-2', + 'maximize', + 'meh', + 'menu', + 'message-circle', + 'message-square', + 'mic-off', + 'mic', + 'minimize-2', + 'minimize', + 'minus-circle', + 'minus-square', + 'minus', + 'monitor', + 'moon', + 'more-horizontal', + 'more-vertical', + 'mouse-pointer', + 'move', + 'music', + 'navigation-2', + 'navigation', + 'octagon', + 'package', + 'paperclip', + 'pause-circle', + 'pause', + 'pen-tool', + 'percent', + 'phone-call', + 'phone-forwarded', + 'phone-incoming', + 'phone-missed', + 'phone-off', + 'phone-outgoing', + 'phone', + 'pie-chart', + 'play-circle', + 'play', + 'plus-circle', + 'plus-square', + 'plus', + 'pocket', + 'power', + 'printer', + 'radio', + 'refresh-ccw', + 'refresh-cw', + 'repeat', + 'rewind', + 'rotate-ccw', + 'rotate-cw', + 'rss', + 'save', + 'scissors', + 'search', + 'send', + 'server', + 'settings', + 'share-2', + 'share', + 'shield-off', + 'shield', + 'shopping-bag', + 'shopping-cart', + 'shuffle', + 'sidebar', + 'skip-back', + 'skip-forward', + 'slack', + 'slash', + 'sliders', + 'smartphone', + 'smile', + 'speaker', + 'square', + 'star', + 'stop-circle', + 'sun', + 'sunrise', + 'sunset', + 'tablet', + 'tag', + 'target', + 'terminal', + 'thermometer', + 'thumbs-down', + 'thumbs-up', + 'toggle-left', + 'toggle-right', + 'tool', + 'trash-2', + 'trash', + 'trello', + 'trending-down', + 'trending-up', + 'triangle', + 'truck', + 'tv', + 'twitch', + 'twitter', + 'type', + 'umbrella', + 'underline', + 'unlock', + 'upload-cloud', + 'upload', + 'user-check', + 'user-minus', + 'user-plus', + 'user-x', + 'user', + 'users', + 'video-off', + 'video', + 'voicemail', + 'volume-1', + 'volume-2', + 'volume-x', + 'volume', + 'watch', + 'wifi-off', + 'wifi', + 'wind', + 'x-circle', + 'x-octagon', + 'x-square', + 'x', + 'youtube', + 'zap-off', + 'zap', + 'zoom-in', + 'zoom-out', +]; +// heroicons v2.0.18 - 292 icons +export const heroicons = [ + 'academic-cap', + 'archive-box-arrow-down', + 'adjustments-vertical', + 'archive-box', + 'arrow-down-circle', + 'archive-box-x-mark', + 'adjustments-horizontal', + 'arrow-down-left', + 'arrow-down-on-square', + 'arrow-down-on-square-stack', + 'arrow-down', + 'arrow-down-right', + 'arrow-left', + 'arrow-left-circle', + 'arrow-down-tray', + 'arrow-long-right', + 'arrow-long-down', + 'arrow-left-on-rectangle', + 'arrow-path', + 'arrow-long-up', + 'arrow-right-circle', + 'arrow-right-on-rectangle', + 'arrow-right', + 'arrow-small-down', + 'arrow-path-rounded-square', + 'arrow-long-left', + 'arrow-small-left', + 'arrow-trending-down', + 'arrow-small-up', + 'arrow-up-left', + 'arrow-trending-up', + 'arrow-up-circle', + 'arrow-up-on-square-stack', + 'arrow-up-on-square', + 'arrow-up-right', + 'arrow-up-tray', + 'arrow-up', + 'arrow-uturn-right', + 'arrow-uturn-up', + 'arrow-top-right-on-square', + 'arrow-uturn-down', + 'arrows-pointing-out', + 'arrow-uturn-left', + 'arrows-pointing-in', + 'arrows-up-down', + 'at-symbol', + 'backspace', + 'backward', + 'banknotes', + 'arrows-right-left', + 'bars-2', + 'bars-3-bottom-left', + 'bars-3-center-left', + 'bars-3', + 'bars-arrow-down', + 'bars-4', + 'bars-arrow-up', + 'battery-0', + 'bars-3-bottom-right', + 'battery-100', + 'bell-alert', + 'bell-slash', + 'battery-50', + 'arrow-small-right', + 'beaker', + 'bell', + 'bolt', + 'bookmark-slash', + 'book-open', + 'bookmark-square', + 'bolt-slash', + 'bookmark', + 'briefcase', + 'building-library', + 'bell-snooze', + 'building-office-2', + 'building-storefront', + 'building-office', + 'calculator', + 'cake', + 'calendar-days', + 'chart-bar-square', + 'chart-bar', + 'camera', + 'bug-ant', + 'calendar', + 'chat-bubble-bottom-center', + 'chart-pie', + 'chat-bubble-left-right', + 'chat-bubble-left-ellipsis', + 'chat-bubble-bottom-center-text', + 'check-circle', + 'check-badge', + 'chat-bubble-oval-left', + 'chat-bubble-left', + 'check', + 'chat-bubble-oval-left-ellipsis', + 'chevron-double-right', + 'chevron-down', + 'chevron-double-down', + 'chevron-double-up', + 'circle-stack', + 'chevron-up-down', + 'chevron-up', + 'clipboard-document-list', + 'chevron-double-left', + 'chevron-right', + 'chevron-left', + 'cloud-arrow-down', + 'cloud-arrow-up', + 'cloud', + 'code-bracket-square', + 'code-bracket', + 'cog-6-tooth', + 'clipboard-document', + 'clock', + 'clipboard-document-check', + 'cog-8-tooth', + 'cog', + 'command-line', + 'computer-desktop', + 'cube-transparent', + 'cpu-chip', + 'credit-card', + 'cube', + 'currency-dollar', + 'currency-bangladeshi', + 'currency-euro', + 'currency-pound', + 'currency-yen', + 'currency-rupee', + 'cursor-arrow-ripple', + 'device-phone-mobile', + 'device-tablet', + 'document-arrow-down', + 'cursor-arrow-rays', + 'document-check', + 'document-chart-bar', + 'document-duplicate', + 'document-minus', + 'clipboard', + 'document-magnifying-glass', + 'document-plus', + 'document', + 'document-text', + 'ellipsis-horizontal-circle', + 'document-arrow-up', + 'ellipsis-horizontal', + 'ellipsis-vertical', + 'eye-dropper', + 'exclamation-triangle', + 'eye-slash', + 'eye', + 'exclamation-circle', + 'envelope-open', + 'face-smile', + 'film', + 'flag', + 'folder-arrow-down', + 'envelope', + 'fire', + 'folder-minus', + 'folder-open', + 'face-frown', + 'folder-plus', + 'forward', + 'funnel', + 'gift-top', + 'folder', + 'gif', + 'globe-alt', + 'finger-print', + 'globe-asia-australia', + 'globe-europe-africa', + 'hand-raised', + 'gift', + 'home', + 'identification', + 'globe-americas', + 'hashtag', + 'inbox-arrow-down', + 'inbox-stack', + 'information-circle', + 'inbox', + 'key', + 'lifebuoy', + 'hand-thumb-down', + 'language', + 'hand-thumb-up', + 'heart', + 'home-modern', + 'light-bulb', + 'lock-closed', + 'magnifying-glass-plus', + 'magnifying-glass', + 'lock-open', + 'magnifying-glass-circle', + 'link', + 'list-bullet', + 'map', + 'map-pin', + 'megaphone', + 'magnifying-glass-minus', + 'minus-circle', + 'musical-note', + 'paint-brush', + 'newspaper', + 'no-symbol', + 'minus-small', + 'paper-airplane', + 'minus', + 'microphone', + 'moon', + 'paper-clip', + 'pause', + 'phone-arrow-up-right', + 'phone-arrow-down-left', + 'phone-x-mark', + 'phone', + 'pencil', + 'play-pause', + 'photo', + 'pencil-square', + 'play', + 'plus-small', + 'plus', + 'power', + 'play-circle', + 'presentation-chart-line', + 'pause-circle', + 'presentation-chart-bar', + 'printer', + 'question-mark-circle', + 'qr-code', + 'queue-list', + 'receipt-percent', + 'radio', + 'receipt-refund', + 'plus-circle', + 'rectangle-group', + 'puzzle-piece', + 'rocket-launch', + 'rectangle-stack', + 'server', + 'rss', + 'scale', + 'server-stack', + 'share', + 'shield-exclamation', + 'shopping-cart', + 'shopping-bag', + 'signal-slash', + 'signal', + 'scissors', + 'shield-check', + 'speaker-wave', + 'speaker-x-mark', + 'squares-plus', + 'star', + 'stop-circle', + 'sun', + 'sparkles', + 'squares-2x2', + 'square-2-stack', + 'square-3-stack-3d', + 'table-cells', + 'ticket', + 'swatch', + 'tag', + 'tv', + 'user-plus', + 'user-minus', + 'stop', + 'user', + 'truck', + 'users', + 'video-camera-slash', + 'user-circle', + 'video-camera', + 'user-group', + 'trophy', + 'viewfinder-circle', + 'variable', + 'trash', + 'view-columns', + 'wifi', + 'window', + 'wrench-screwdriver', + 'wrench', + 'x-mark', + 'wallet', + 'x-circle', +]; diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts new file mode 100644 index 0000000..86c5747 --- /dev/null +++ b/src/app/services/auth.service.ts @@ -0,0 +1,23 @@ +import { Injectable } from '@angular/core'; + import { Router } from '@angular/router'; +import { SignerService } from './signer.service'; + + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor( + private signerService: SignerService, + private router: Router + ) { } + + isLoggedIn() { + if (this.signerService.getPublicKey()) { + return true; + } + this.router.navigate(['/sign-in']); + return false; + } +} diff --git a/src/app/services/content-parser.service.ts b/src/app/services/content-parser.service.ts new file mode 100644 index 0000000..fe98144 --- /dev/null +++ b/src/app/services/content-parser.service.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@angular/core'; +import { SignerService } from './signer.service'; + + + +@Injectable({ + providedIn: 'root' +}) +export class ContentParserService { + + constructor( + private signerService: SignerService + ) { } + +} diff --git a/src/app/services/emoji.service.ts b/src/app/services/emoji.service.ts new file mode 100644 index 0000000..131e029 --- /dev/null +++ b/src/app/services/emoji.service.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class EmojiService { + private readonly EMOJI_URL = 'data/emoji.json'; + + constructor(private http: HttpClient) { + console.log('EmojiService initialized'); + } + + getEmojis(): Observable { + console.log('Fetching emojis from:', this.EMOJI_URL); + return this.http.get(this.EMOJI_URL); + } +} diff --git a/src/app/services/event-emitter.service.ts b/src/app/services/event-emitter.service.ts new file mode 100644 index 0000000..971862f --- /dev/null +++ b/src/app/services/event-emitter.service.ts @@ -0,0 +1,6 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class EventEmitterService {} \ No newline at end of file diff --git a/src/app/services/gif.service.ts b/src/app/services/gif.service.ts new file mode 100644 index 0000000..cb31cd5 --- /dev/null +++ b/src/app/services/gif.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams, } from '@angular/common/http'; +import { Observable} from 'rxjs'; +import { TenorResponse } from 'app/types/gif'; + +@Injectable({ + providedIn: 'root' +}) +export class GifService { + + constructor( + private http: HttpClient + ) { } + + async getTopGifs(search: string, apiKey: string): Promise> { + const url = "https://g.tenor.com/v1/search" + const params = new HttpParams().append('key', apiKey).append('q', search); + return this.http.get(url, {params: params}); + } +} diff --git a/src/app/services/image-service.service.ts b/src/app/services/image-service.service.ts new file mode 100644 index 0000000..79d7c8b --- /dev/null +++ b/src/app/services/image-service.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class ImageServiceService { + + constructor(private http: HttpClient) { } + + uploadImage(file: File | Blob): Observable { + // nostrimg + // returns url of uploaded image + let url = "https://nostrimg.com/api/upload"; + const fd = new FormData(); + fd.append("image", file); + // fd.append("submit", "Upload Image"); + return this.http.post(url, fd); + } + + // this is broken cuz something changed on server + // not allowed accept from other clients not this + uploadImageNostrBuild(file: File | Blob): Observable { + // returns url of uploaded image + let url = "https://nostrimg.com/api/upload"; + const fd = new FormData(); + fd.append("fileToUpload", file); + // fd.append("submit", "Upload Image"); + return this.http.post(url, fd); + } +} diff --git a/src/app/services/indexer.service.ts b/src/app/services/indexer.service.ts new file mode 100644 index 0000000..ba8f364 --- /dev/null +++ b/src/app/services/indexer.service.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@angular/core'; + +@Injectable({ + providedIn: 'root' +}) +export class IndexerService { + + private mainnetLocalStorageKey = 'mainnetIndexers'; + private testnetLocalStorageKey = 'testnetIndexers'; + private mainnetPrimaryIndexerKey = 'mainnetPrimaryIndexer'; + private testnetPrimaryIndexerKey = 'testnetPrimaryIndexer'; + + private networkStorageKey = 'selectedNetwork'; + + private defaultMainnetIndexer = 'https://btc.indexer.angor.io/'; + private defaultTestnetIndexer = 'https://tbtc.indexer.angor.io/'; + + constructor() { + this.initializeDefaultIndexers(); + } + + private initializeDefaultIndexers(): void { + if (this.getIndexers('mainnet').length === 0) { + this.addIndexer(this.defaultMainnetIndexer, 'mainnet'); + this.setPrimaryIndexer(this.defaultMainnetIndexer, 'mainnet'); + } + if (this.getIndexers('testnet').length === 0) { + this.addIndexer(this.defaultTestnetIndexer, 'testnet'); + this.setPrimaryIndexer(this.defaultTestnetIndexer, 'testnet'); + } + } + + addIndexer(indexer: string, network: 'mainnet' | 'testnet'): void { + let indexers = this.getIndexers(network); + if (!indexers.includes(indexer)) { + indexers.push(indexer); + this.saveIndexers(indexers, network); + } + } + + getIndexers(network: 'mainnet' | 'testnet'): string[] { + const storageKey = network === 'mainnet' ? this.mainnetLocalStorageKey : this.testnetLocalStorageKey; + return JSON.parse(localStorage.getItem(storageKey) || '[]'); + } + + private saveIndexers(indexers: string[], network: 'mainnet' | 'testnet'): void { + const storageKey = network === 'mainnet' ? this.mainnetLocalStorageKey : this.testnetLocalStorageKey; + localStorage.setItem(storageKey, JSON.stringify(indexers)); + } + + setPrimaryIndexer(indexer: string, network: 'mainnet' | 'testnet'): void { + if (this.getIndexers(network).includes(indexer)) { + const primaryKey = network === 'mainnet' ? this.mainnetPrimaryIndexerKey : this.testnetPrimaryIndexerKey; + localStorage.setItem(primaryKey, indexer); + } + } + + getPrimaryIndexer(network: 'mainnet' | 'testnet'): string | null { + const primaryKey = network === 'mainnet' ? this.mainnetPrimaryIndexerKey : this.testnetPrimaryIndexerKey; + return localStorage.getItem(primaryKey); + } + + removeIndexer(indexer: string, network: 'mainnet' | 'testnet'): void { + let indexers = this.getIndexers(network); + const index = indexers.indexOf(indexer); + if (index !== -1) { + indexers.splice(index, 1); + this.saveIndexers(indexers, network); + if (indexer === this.getPrimaryIndexer(network)) { + const primaryKey = network === 'mainnet' ? this.mainnetPrimaryIndexerKey : this.testnetPrimaryIndexerKey; + localStorage.removeItem(primaryKey); + } + } + } + + clearAllIndexers(network: 'mainnet' | 'testnet'): void { + const storageKey = network === 'mainnet' ? this.mainnetLocalStorageKey : this.testnetLocalStorageKey; + const primaryKey = network === 'mainnet' ? this.mainnetPrimaryIndexerKey : this.testnetPrimaryIndexerKey; + localStorage.removeItem(storageKey); + localStorage.removeItem(primaryKey); + } + setNetwork(network: 'mainnet' | 'testnet'): void { + localStorage.setItem(this.networkStorageKey, network); + } + + getNetwork(): 'mainnet' | 'testnet' { + return (localStorage.getItem(this.networkStorageKey) as 'mainnet' | 'testnet') || 'testnet'; + } +} diff --git a/src/app/services/lightning.service.ts b/src/app/services/lightning.service.ts new file mode 100644 index 0000000..5f09c72 --- /dev/null +++ b/src/app/services/lightning.service.ts @@ -0,0 +1,97 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable, of} from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { LightningResponse, LightningInvoice } from '../types/post'; +import { Event } from 'nostr-tools'; + + +@Injectable({ + providedIn: 'root' +}) +export class LightningService { + + constructor( + private http: HttpClient + ) { } + + getLightning(url: string): Observable { + return this.http.get(url) + .pipe( + catchError(() => of({status: "Failed"} as LightningResponse)) + ) + } + + getLightningInvoice(url: string, amount: string): Observable { + // amount is in millisats so 5 sats === 5000 + let aUrl = `${url}?amount=${amount}`; + return this.http.get(aUrl); + } + + // url would be something like `brah@npubkey.com` + // which needs to be processed to make the actual request + getLightningAddress(url: string): string { + const splitUrl = url.split("@"); + const username = splitUrl[0]; + const domain = splitUrl[1]; + return `https://${domain}/.well-known/lnurlp/${username}`; + } + + /* + example http post to callback if nostr allowed + { + "kind": 9734, + "content": "Zap!", + "tags": [ + ["relays", "wss://nostr-pub.wellorder.com"], + ["amount", "21000"], + ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"], + ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"], + ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"] + ], + "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", + "created_at": 1679673265, + "id": "30efed56a035b2549fcaeec0bf2c1595f9a9b3bb4b1a38abaf8ee9041c4b7d93", + "sig": "f2cb581a84ed10e4dc84937bd98e27acac71ab057255f6aa8dfa561808c981fe8870f4a03c1e3666784d82a9c802d3704e174371aa13d63e2aeaf24ff5374d9d" + } + */ + sendZapRequest(callback: string, zapRequest: Event, amount: string, lnurl: string): Observable { + const event: string = encodeURI(JSON.stringify((zapRequest))); + const url = `${callback}?amount=${amount}&nostr=${event}&lnurl=${lnurl}` + return this.http.get(url); + } + + async login(): Promise { + if (window.webln && !window.webln.isEnabled()) { + await window.webln.enable(); + } + return true; + } + + hasWebln() { + if (window.webln) { + return true; + } + return false; + } + + async sendPayment(pr: string) { + return await window.webln?.sendPayment(pr) + } + + async payInvoice(pr: string) { + await this.login(); + if (this.hasWebln()) { + const rsp = await this.sendPayment(pr).catch((error) => { + return false; + }); + if (rsp) { + return true; + } + return false; + } else { + console.log("WebLN not available"); + return false; + } + } +} diff --git a/src/app/services/link-preview.service.ts b/src/app/services/link-preview.service.ts new file mode 100644 index 0000000..6ebf0b4 --- /dev/null +++ b/src/app/services/link-preview.service.ts @@ -0,0 +1,25 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Preview } from '../types/preview'; +import { map } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class LinkPreviewService { + + private _accessKey = '5b54e80a65c77848ceaa4630331e8384950e09d392365'; + private _apiURL = 'https://api.linkpreview.net/'; + + constructor(private http: HttpClient) {} + + fetchLink(url: string): Observable { + console.log('fetching the following link: ', url); + const params = new HttpParams() + .append('key', this._accessKey) + .append('q', url); + + return this.http.get(this._apiURL, {params: params}).pipe(map(value => value as Preview)); + } +} diff --git a/src/app/services/nip05.service.ts b/src/app/services/nip05.service.ts new file mode 100644 index 0000000..6c2ee42 --- /dev/null +++ b/src/app/services/nip05.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@angular/core'; +import { NIP05 } from '../types/nostr'; +import { Observable, of} from 'rxjs'; +import { HttpClient } from '@angular/common/http'; +import { catchError } from 'rxjs/operators'; + +@Injectable({ + providedIn: 'root' +}) +export class Nip05Service { + + constructor( + private http: HttpClient + ) { } + + // url would be something like `brah@npubkey.com` + // which needs to be processed to make the actual request + getNIP05Url(url: string): string { + // https:///.well-known/nostr.json?name= + const splitUrl = url.split("@"); + const username = splitUrl[0]; + const domain = splitUrl[1]; + return `https://${domain}/.well-known/nostr.json?name=${username}`; + } + + getNIP05(nip05: string): Observable { + const url = this.getNIP05Url(nip05); + return this.http.get(url) + } +} diff --git a/src/app/services/nostr-band-api.service.ts b/src/app/services/nostr-band-api.service.ts new file mode 100644 index 0000000..bc293c8 --- /dev/null +++ b/src/app/services/nostr-band-api.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { Kind0Content } from '../types/user'; +import { withCache } from '@ngneat/cashew'; + +interface Profile { + pubkey: string; + new_followers_count: number; + relays: Array; + profile: Kind0Content +} + +@Injectable({ + providedIn: 'root' +}) +export class NostrBandApiService { + + baseUrl: string = "https://api.nostr.band/"; + + trending: string = "v0/trending/"; + profiles: string = "profiles"; + notes: string = "notes"; + + constructor( + private http: HttpClient + ) { } + + getTrendingProfiles(): Observable{ + const url = `${this.baseUrl}${this.trending}${this.profiles}`; + return this.http.get(url, {context: withCache()}); + } + + getTrendingNotes(): Observable{ + const url = `${this.baseUrl}${this.trending}${this.notes}`; + return this.http.get(url, {context: withCache()}); + } +} diff --git a/src/app/services/nostr.service.ts b/src/app/services/nostr.service.ts new file mode 100644 index 0000000..9fb21c2 --- /dev/null +++ b/src/app/services/nostr.service.ts @@ -0,0 +1,899 @@ +import { Injectable } from '@angular/core'; +import { + generateSecretKey, + getPublicKey, + finalizeEvent, + verifyEvent, + Event as NostrEvent +} from 'nostr-tools/pure'; +import { bytesToHex, hexToBytes } from '@noble/hashes/utils'; +import { sha256 } from '@noble/hashes/sha256'; +import { RelayService } from './relay.service'; +import { Filter,kinds } from 'nostr-tools'; +import { nip04 } from 'nostr-tools'; +import { SecurityService } from './security.service'; +import { Observable, of, Subject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; +import { EncryptedDirectMessage } from 'nostr-tools/kinds'; +import { mergeMap, bufferTime } from 'rxjs/operators'; + + +interface CustomMessageEvent { + isSentByUser: boolean; + decryptedMessage: string; + createdAt: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class NostrService { + private secretKey: Uint8Array; + private publicKey: string; + private eventSubject = new Subject(); + private notificationSubject = new Subject(); + private messageSubject = new Subject(); + private currentPage = 0; + private messagesPerPage = 50; + private allDecryptedMessages: CustomMessageEvent[] = []; + private isProcessing = false; + + private isDecrypting = false; + private messageQueue: NostrEvent[] = []; + + private latestMessageTimestamps: { [pubKey: string]: number } = {}; + private processedEventIds = new Set(); + private chatList: { + pubKey: string; + lastMessage: string; + lastMessageTime: number; + metadata?: any; + }[] = []; + + private chatListSubject = new BehaviorSubject<{ + pubKey: string; + lastMessage: string; + lastMessageTime: number; + metadata?: any; + }[]>(this.chatList); + nostrPublicKey = ''; + nostrSignedEvent = ''; + nostrRelays?: string[]; + + nostrEvent = { + created_at: Date.now(), + kind: 1, + tags: [], + content: 'This is my nostr message', + pubkey: '', + }; + + // Observable that other parts of the app can subscribe to + public eventUpdates$ = this.eventSubject.asObservable(); + + constructor( + private relayService: RelayService, + private security: SecurityService, + ) { + this.secretKey = generateSecretKey(); + this.publicKey = getPublicKey(this.secretKey); + } + + + // Account management + generateNewAccount(): { publicKey: string; secretKeyHex: string } { + this.secretKey = generateSecretKey(); + this.publicKey = getPublicKey(this.secretKey); + return { + publicKey: this.publicKey, + secretKeyHex: bytesToHex(this.secretKey), + }; + } + + getKeys(): { secretKey: Uint8Array; publicKey: string } { + return { + + secretKey: this.secretKey, + publicKey: this.publicKey, + }; + } + + getSecretKeyHex(): string { + return bytesToHex(this.secretKey); + } + + getPublicKeyHex(): string { + return this.publicKey; + } + + getUserPublicKey(): string | null { + return localStorage.getItem('nostrPublicKey'); + } + + // Signing events + async signEventWithPassword( + content: string, + encryptedPrivateKey: string, + password: string, + kind: number, + tags: string[][], + pubkey: string + ): Promise { + const decryptedPrivateKey = await this.security.decryptData(encryptedPrivateKey, password); + const secretKey = hexToBytes(decryptedPrivateKey); + + if (!this.isValidHex(bytesToHex(secretKey))) { + console.error('Invalid secret key provided:', bytesToHex(secretKey)); + throw new Error('Invalid secret key format'); + } + + const event = this.createEvent(content, kind, tags, pubkey); + + const signedEvent = finalizeEvent(event, secretKey); + + if (!this.isValidHex(signedEvent.id)) { + console.error('Invalid signed event ID:', signedEvent.id); + throw new Error('Invalid signed event format'); + } + + return signedEvent; + } + + + async signEventWithExtension(content: string, kind: number, tags: string[][], pubkey: string): Promise { + const gt = globalThis as any; + + if (!gt.nostr || typeof gt.nostr.signEvent !== 'function') { + throw new Error('Nostr extension not available or signEvent method is missing.'); + } + + const event = this.createEvent(content, kind, tags, pubkey); + + try { + const signedEvent = await gt.nostr.signEvent(event); + return signedEvent; + } catch (error) { + console.error('Error signing event with extension:', error); + throw error; + } + } + + + async signEvent( + eventContent: string, + kind: number, + options: { + encryptedPrivateKey?: string; + password?: string; + useExtension?: boolean; + tags: string[][]; + pubkey: string; + } + ): Promise { + const { encryptedPrivateKey, password, useExtension, tags, pubkey } = options; + + if (useExtension) { + return this.signEventWithExtension(eventContent, kind, tags, pubkey); + } + + if (encryptedPrivateKey && password) { + return this.signEventWithPassword(eventContent, encryptedPrivateKey, password, kind, tags, pubkey); + } + + throw new Error('No valid signing method provided.'); + } + + // Event management + private createEvent(content: string, kind: number, tags: string[][], pubkey: string): NostrEvent { + return { + kind, + created_at: Math.floor(Date.now() / 1000), + tags, + content, + pubkey, + id: '', + sig: '', + } as unknown as NostrEvent; + } + + + async getEventId(event: NostrEvent): Promise { + const eventSerialized = JSON.stringify([ + 0, + event.pubkey, + event.created_at, + event.kind, + event.tags, + event.content, + ]); + return bytesToHex(await sha256(eventSerialized)); + } + + serializeEvent(event: any): string { + return JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]); + } + + getEventHash(event: any): string { + const utf8Encoder = new TextEncoder(); + const eventHash = sha256(utf8Encoder.encode(this.serializeEvent(event))); + return this.bytesToHex(eventHash); + } + + verifyEvent(event: NostrEvent): boolean { + return verifyEvent(event); + } + + + + // Messaging (NIP-04) + async decryptMessageWithExtension(encryptedContent: string, senderPubKey: string): Promise { + try { + const gt = globalThis as any; + const decryptedMessage = await gt.nostr.nip04.decrypt(senderPubKey, encryptedContent); + return decryptedMessage; + } catch (error) { + console.error('Error decrypting message with extension:', error); + throw new Error('Failed to decrypt message with Nostr extension.'); + } + } + + + async encryptMessageWithExtension(content: string, pubKey: string): Promise { + const gt = globalThis as any; + const encryptedMessage = await gt.nostr.nip04.encrypt(pubKey, content); + return encryptedMessage; + } + + async encryptMessage(privateKey: string, recipientPublicKey: string, message: string): Promise { + console.log(message); + try { + const encryptedMessage = await nip04.encrypt(privateKey, recipientPublicKey, message); + return encryptedMessage; + } catch (error) { + console.error('Error encrypting message:', error); + throw error; + } + } + + // NIP-04: Decrypting Direct Messages + async decryptMessage(privateKey: string, senderPublicKey: string, encryptedMessage: string): Promise { + try { + const decryptedMessage = await nip04.decrypt(privateKey, senderPublicKey, encryptedMessage); + return decryptedMessage; + } catch (error) { + console.error('Error decrypting message:', error); + throw error; + } + } + + + // Profile management + async updateProfile( + name: string | null, + about: string | null, + picture: string | null, + tags: string[][] = [], + pubkey: string | null = null + ): Promise { + const content = JSON.stringify({ name, about, picture }); + + const finalPubkey = pubkey || this.getPublicKeyHex(); + + const event = this.createEvent(content, 0, tags, finalPubkey); + + return this.publishEventToRelays(event); + } + + + async getUserProfile(pubkey: string): Promise { + const metadata = await this.fetchMetadata(pubkey); + const user: any = { + nostrPubKey: pubkey, + displayName: metadata.name, + picture: metadata.picture, + about: metadata.about, + website: metadata.website, + lud16: metadata.lud16, + nip05: metadata.nip05, + }; + return user; + } + + async fetchMetadata(pubkey: string): Promise { + await this.ensureRelaysConnected(); + const pool = this.relayService.getPool(); + const connectedRelays = this.relayService.getConnectedRelays(); + + if (connectedRelays.length === 0) { + throw new Error('No connected relays'); + } + + return new Promise((resolve, reject) => { + const sub = pool.subscribeMany( + connectedRelays, + [{ authors: [pubkey], kinds: [0] }], + { + onevent: (event: NostrEvent) => { + if (event.pubkey === pubkey && event.kind === 0) { + try { + const content = JSON.parse(event.content); + resolve(content); + } catch (error) { + console.error('Error parsing event content:', error); + resolve(null); + } finally { + sub.close(); + } + } + }, + oneose() { + sub.close(); + resolve(null); + }, + } + ); + }); + } + + // Social interactions + async getFollowers(pubkey: string): Promise { + await this.ensureRelaysConnected(); + const pool = this.relayService.getPool(); + const connectedRelays = this.relayService.getConnectedRelays(); + + if (connectedRelays.length === 0) { + throw new Error('No connected relays'); + } + + const filters: Filter[] = [{ kinds: [3], '#p': [pubkey] }]; + const followers: any[] = []; + + return new Promise((resolve) => { + const sub = pool.subscribeMany(connectedRelays, filters, { + onevent: (event: NostrEvent) => { + followers.push({ nostrPubKey: event.pubkey }); + this.eventSubject.next(event); // Emit the event to subscribers + }, + oneose() { + sub.close(); + resolve(followers); + }, + }); + }); + } + + async getFollowing(pubkey: string): Promise { + await this.ensureRelaysConnected(); + const pool = this.relayService.getPool(); + const connectedRelays = this.relayService.getConnectedRelays(); + + if (connectedRelays.length === 0) { + throw new Error('No connected relays'); + } + + const filters: Filter[] = [{ kinds: [3], authors: [pubkey] }]; + const following: any[] = []; + + return new Promise((resolve) => { + const sub = pool.subscribeMany(connectedRelays, filters, { + onevent: (event: NostrEvent) => { + const tags = event.tags.filter((tag) => tag[0] === 'p'); + tags.forEach((tag) => { + following.push({ nostrPubKey: tag[1] }); + this.eventSubject.next(event); // Emit the event to subscribers + }); + }, + oneose() { + sub.close(); + resolve(following); + }, + }); + }); + } + + + + async getEventsByAuthor( + pubkey: string, + kinds: number[] = [1] + ): Promise { + await this.ensureRelaysConnected(); + const pool = this.relayService.getPool(); + const connectedRelays = this.relayService.getConnectedRelays(); + + if (connectedRelays.length === 0) { + throw new Error('No connected relays'); + } + + const filters: Filter[] = [{ authors: [pubkey], kinds }]; + + return new Promise((resolve) => { + const events: NostrEvent[] = []; + const sub = pool.subscribeMany(connectedRelays, filters, { + onevent: (event: NostrEvent) => { + events.push(event); + this.eventSubject.next(event); // Emit the event to subscribers + }, + oneose() { + sub.close(); + resolve(events); + }, + }); + }); + } + + // Relay management + private async ensureRelaysConnected(): Promise { + await this.relayService.ensureConnectedRelays(); + } + + async publishEventToRelays(event: NostrEvent): Promise { + await this.ensureRelaysConnected(); + const pool = this.relayService.getPool(); + const connectedRelays = this.relayService.getConnectedRelays(); + + if (connectedRelays.length === 0) { + throw new Error('No connected relays'); + } + + const publishPromises = connectedRelays.map(async (relayUrl) => { + try { + await pool.publish([relayUrl], event); + console.log(`Event published to relay: ${relayUrl}`); + this.eventSubject.next(event); // Emit the event to subscribers + return event; + } catch (error) { + console.error(`Failed to publish event to relay: ${relayUrl}`, error); + throw error; + } + }); + + try { + await Promise.any(publishPromises); + return event; + } catch (aggregateError) { + console.error('Failed to publish event: AggregateError', aggregateError); + this.handlePublishFailure(aggregateError); + throw aggregateError; + } + } + + private handlePublishFailure(error: unknown): void { + if (error instanceof AggregateError) { + console.error('All relays failed to publish the event. Retrying...'); + } else { + console.error('An unexpected error occurred:', error); + } + } + + + //events ================================================================ + +subscribeToEvents(pubkey: string): void { + this.relayService.ensureConnectedRelays().then(() => { + const filter: Filter = { + kinds: [1], // Kind 1 represents text notes + authors: [pubkey], + limit: 20 + }; + + this.relayService.subscribeToFilter(filter); + + // Sort and process events using RxJS operators + this.relayService.getEventStream() + .pipe( + bufferTime(1000), // Collect events over 1 second intervals + mergeMap(events => this.processAndSortEvents(events)) // Process and sort events + ) + .subscribe((sortedEvents: NostrEvent[]) => { + sortedEvents.forEach(event => this.eventSubject.next(event)); + }); + }); + } + + // Generic method to process and sort events by `created_at` + private processAndSortEvents(events: NostrEvent[]): Observable { + const sortedEvents = events.sort((a, b) => b.created_at - a.created_at); + return of(sortedEvents); + } + + + //notification================================================================ + + subscribeToNotifications(pubkey: string): void { + this.relayService.ensureConnectedRelays().then(() => { + // Define a filter for notifications + const filter: Filter = { + kinds: [1, 4], // Kind 1 for text notes, kind 4 for encrypted direct messages + '#p': [pubkey], // Events tagged with the user's public key + limit: 50 + }; + + this.relayService.subscribeToFilter(filter); + + this.relayService.getEventStream().subscribe((event) => { + if (this.isNotificationEvent(event, pubkey)) { + // Get the UNIX timestamp from event and convert it to readable date + const eventTimestamp = event.created_at * 1000; // Convert seconds to milliseconds + const eventDate = new Date(eventTimestamp); + + // Format the date and time (example: 2024-09-07 14:30) + const formattedDate = eventDate.toLocaleString(); + + // Add message based on the kind of the event + if (event.kind === 4) { + event.content = `Sent a private message at ${formattedDate}.`; + } else if (event.kind === 1) { + event.content = `Mentioned you in an event at ${formattedDate}.`; + } + + // Push the updated event to the notificationSubject + this.notificationSubject.next(event); + } + }); + }); + } + + // Check if the event is a notification for the user + private isNotificationEvent(event: NostrEvent, pubkey: string): boolean { + return event.tags.some(tag => tag[0] === 'p' && tag[1] === pubkey); + } + + // Get the notification event stream + getNotificationStream() { + return this.notificationSubject.asObservable(); + } + + getEventStream() { + return this.relayService.getEventStream(); + } + + //Nostr Extension Interactions======================================================= + async getNostrPublicKeyFromExtension() { + const gt = globalThis as any; + const pubKey = await gt.nostr.getPublicKey(); + this.nostrPublicKey = pubKey; + this.nostrEvent.pubkey = this.nostrPublicKey; + } + + async getNostrPublicRelaysFromExtension() { + const gt = globalThis as any; + const relays = await gt.nostr.getRelays(); + this.nostrRelays = relays; + } + + // utility methods ================================================================ + isValidHex(hexString: string): boolean { + return /^[0-9a-fA-F]+$/.test(hexString) && hexString.length % 2 === 0; + } + + async decryptPrivateKeyWithPassword(encryptedPrivateKey: string, password: string): Promise { + try { + const decryptedPrivateKey = await this.security.decryptData(encryptedPrivateKey, password); + return decryptedPrivateKey; + } catch (error) { + console.error('Error decrypting private key with password:', error); + throw new Error('Failed to decrypt private key with the provided password.'); + } + } + + bytesToHex(bytes: Uint8Array): string { + return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join(''); + } + //Mwssages============================================================ + + subscribeToKind4Messages( + pubkey: string, + recipientPublicKey: string, + useExtension: boolean, + decryptedSenderPrivateKey: string + ): void { + this.currentPage = 0; // Reset to first page + this.allDecryptedMessages = []; // Reset the list of decrypted messages + this.processedEventIds.clear(); // Reset the set of processed event IDs + this.loadMessages(pubkey, recipientPublicKey, useExtension, decryptedSenderPrivateKey, this.currentPage); + this.subscribeToRealTimeMessages(pubkey, recipientPublicKey, useExtension, decryptedSenderPrivateKey); + } + + private loadMessages( + pubkey: string, + recipientPublicKey: string, + useExtension: boolean, + decryptedSenderPrivateKey: string, + page: number + ): void { + this.relayService.ensureConnectedRelays().then(() => { + const filters: Filter[] = [ + { + kinds: [4], + authors: [pubkey], + '#p': [recipientPublicKey], + limit: this.messagesPerPage, + until: this.getPaginationTime(page), + }, + { + kinds: [4], + authors: [recipientPublicKey], + '#p': [pubkey], + limit: this.messagesPerPage, + until: this.getPaginationTime(page), + }, + ]; + + this.relayService.getPool().subscribeMany(this.relayService.getConnectedRelays(), filters, { + onevent: (event: NostrEvent) => { + if (!this.processedEventIds.has(event.id) && !this.messageQueue.some(e => e.id === event.id)) { + this.messageQueue.push(event); + this.processQueue(pubkey, useExtension, decryptedSenderPrivateKey, recipientPublicKey); + } + }, + oneose: () => { + console.log('Subscription closed'); + }, + }); + }); + } + + private getPaginationTime(page: number): number { + if (page === 0) { + return Math.floor(Date.now() / 1000); + + } + + const oldestMessage = this.getOldestMessageTimestamp(); + return oldestMessage ? oldestMessage : Math.floor(Date.now() / 1000); + } + + loadMoreMessages(pubkey: string, recipientPublicKey: string, useExtension: boolean, decryptedSenderPrivateKey: string): void { + this.currentPage++; + this.loadMessages(pubkey, recipientPublicKey, useExtension, decryptedSenderPrivateKey, this.currentPage); + } + + private subscribeToRealTimeMessages( + pubkey: string, + recipientPublicKey: string, + useExtension: boolean, + decryptedSenderPrivateKey: string + ): void { + this.relayService.ensureConnectedRelays().then(() => { + const filters: Filter[] = [ + { + kinds: [4], + authors: [pubkey], + '#p': [recipientPublicKey], + }, + { + kinds: [4], + authors: [recipientPublicKey], + '#p': [pubkey], + }, + ]; + + this.relayService.getPool().subscribeMany(this.relayService.getConnectedRelays(), filters, { + onevent: (event: NostrEvent) => { + if (!this.processedEventIds.has(event.id) && !this.messageQueue.some(e => e.id === event.id)) { + this.messageQueue.push(event); + this.processQueue(pubkey, useExtension, decryptedSenderPrivateKey, recipientPublicKey); + } + }, + oneose: () => { + console.log('Real-time subscription closed'); + }, + }); + }); + } + + private async processQueue( + pubkey: string, + useExtension: boolean, + decryptedSenderPrivateKey: string, + recipientPublicKey: string + ): Promise { + if (this.isProcessing) { + console.log('Processing is already in progress, waiting for the current batch to finish...'); + return; + } + + this.isProcessing = true; + + try { + while (this.messageQueue.length > 0) { + const event = this.messageQueue.shift(); + if (event && !this.processedEventIds.has(event.id)) { + console.log(`Processing event with ID: ${event.id}`); + + try { + const decryptedMessage = await this.decryptReceivedMessage( + event, + useExtension, + decryptedSenderPrivateKey, + recipientPublicKey + ); + + if (decryptedMessage) { + const messageTimestamp = event.created_at * 1000; + const customMessage: CustomMessageEvent = { + isSentByUser: event.pubkey === pubkey, + decryptedMessage, + createdAt: messageTimestamp, + }; + + this.allDecryptedMessages.push(customMessage); + this.allDecryptedMessages.sort((a, b) => a.createdAt - b.createdAt); + this.messageSubject.next(customMessage); + this.processedEventIds.add(event.id); + + } else { + console.warn(`Decrypted message is empty for event ID: ${event.id}`); + } + } catch (decryptError) { + console.error(`Failed to decrypt event with ID: ${event.id}`, decryptError); + } + } else { + console.log(`Event with ID: ${event?.id} has already been processed or is invalid.`); + } + } + } catch (queueError) { + console.error('An error occurred while processing the message queue:', queueError); + } finally { + this.isProcessing = false; + console.log('Finished processing the message queue.'); + } + + if (this.messageQueue.length > 0) { + console.log('Re-triggering processQueue as there are more messages in the queue...'); + this.processQueue(pubkey, useExtension, decryptedSenderPrivateKey, recipientPublicKey); + } + } + + + + private async decryptReceivedMessage( + event: NostrEvent, + useExtension: boolean, + decryptedSenderPrivateKey: string, + recipientPublicKey: string + ): Promise { + if (useExtension) { + return await this.decryptMessageWithExtension(event.content, recipientPublicKey); + } else { + return await this.decryptMessage(decryptedSenderPrivateKey, recipientPublicKey, event.content); + } + } + + private getOldestMessageTimestamp(): number | null { + if (this.allDecryptedMessages.length === 0) { + return null; + } + + return this.allDecryptedMessages.reduce((oldest, message) => { + return message.createdAt < oldest ? message.createdAt : oldest; + }, this.allDecryptedMessages[0].createdAt); + } + + getMessageStream(): Observable { + return this.messageSubject.asObservable(); + } + + + //Chat list============================================================ + + subscribeToChatList( + pubkey: string, + useExtension: boolean, + decryptedSenderPrivateKey: string + ): void { + this.relayService.ensureConnectedRelays().then(() => { + const filters: Filter[] = [ + { + kinds: [EncryptedDirectMessage], + authors: [pubkey], + }, + { + kinds: [EncryptedDirectMessage], + '#p': [pubkey] + } + ]; + + this.relayService.getPool().subscribeMany(this.relayService.getConnectedRelays(), filters, { + onevent: async (event: NostrEvent) => { + const otherPartyPubKey = event.pubkey === pubkey + ? event.tags.find(tag => tag[0] === 'p')?.[1] || '' + : event.pubkey; + + if (!otherPartyPubKey) { + return; + } + + const lastTimestamp = this.latestMessageTimestamps[otherPartyPubKey] || 0; + + if (event.created_at > lastTimestamp) { + this.latestMessageTimestamps[otherPartyPubKey] = event.created_at; + this.messageQueue.push(event); + this.processNextMessage(pubkey, useExtension, decryptedSenderPrivateKey); + } + }, + oneose: () => { + console.log('Subscription closed'); + this.chatListSubject.next(this.chatList); + } + }); + }); + } + + private async processNextMessage(pubkey: string, useExtension: boolean, decryptedSenderPrivateKey: string): Promise { + if (this.isDecrypting || this.messageQueue.length === 0) { + return; + } + + this.isDecrypting = true; + const event = this.messageQueue.shift(); + + if (!event) { + this.isDecrypting = false; + return; + } + + const isSentByUser = event.pubkey === pubkey; + const otherPartyPubKey = isSentByUser + ? event.tags.find(tag => tag[0] === 'p')?.[1] || '' + : event.pubkey; + + if (!otherPartyPubKey) { + this.isDecrypting = false; + return; + } + + try { + const decryptedMessage = await this.decryptReceivedMessage( + event, + useExtension, + decryptedSenderPrivateKey, + otherPartyPubKey + ); + + if (decryptedMessage) { + const messageTimestamp = event.created_at * 1000; + this.addOrUpdateChatList(otherPartyPubKey, decryptedMessage, messageTimestamp); + } + } catch (error) { + console.error('Failed to decrypt message:', error); + } finally { + this.isDecrypting = false; + this.processNextMessage(pubkey, useExtension, decryptedSenderPrivateKey); + } + } + + private addOrUpdateChatList(pubKey: string, message: string, createdAt: number): void { + const existingItem = this.chatList.find(item => item.pubKey === pubKey); + + if (existingItem) { + if (existingItem.lastMessageTime < createdAt) { + existingItem.lastMessage = message; + existingItem.lastMessageTime = createdAt; + } + } else { + this.chatList.push({ pubKey, lastMessage: message, lastMessageTime: createdAt }); + this.fetchMetadataForPubKey(pubKey); + } + + this.chatList.sort((a, b) => b.lastMessageTime - a.lastMessageTime); + } + + private fetchMetadataForPubKey(pubKey: string): void { + this.fetchMetadata(pubKey).then(metadata => { + const existingItem = this.chatList.find(item => item.pubKey === pubKey); + if (existingItem && metadata) { + existingItem.metadata = metadata; + this.chatListSubject.next(this.chatList); + } + }).catch(error => { + console.error(`Failed to fetch metadata for pubKey: ${pubKey}`, error); + }); + } + + + getChatListStream() { + return this.chatListSubject.asObservable(); + } +} diff --git a/src/app/services/nostrTemp.service.ts b/src/app/services/nostrTemp.service.ts new file mode 100644 index 0000000..a6bb56d --- /dev/null +++ b/src/app/services/nostrTemp.service.ts @@ -0,0 +1,643 @@ +import { Injectable } from '@angular/core'; + +import { Event, Filter, nip10, UnsignedEvent, getEventHash, finalizeEvent } from 'nostr-tools'; +import { User } from '../types/user'; +import { Post, Zap } from '../types/post'; +import { SignerService } from './signer.service'; +import { NgxIndexedDBService } from 'ngx-indexed-db'; +import { DBUser, dbUserToUser } from '../types/user'; +import { SimplePool } from 'nostr-tools' +import { hexToBytes } from '@noble/hashes/utils' + +@Injectable({ + providedIn: 'root' +}) +export class NostrTempService { + + constructor( + private signerService: SignerService, + private dbService: NgxIndexedDBService + ) { } + + // db stuff idk why its in this file -- todo fix + storeUsersInDB(users: User[]) { + this.dbService.bulkAdd('users', users); + } + + storeNotificationsInDB(notifications: Zap[]) { + this.dbService.bulkAdd('notifications', notifications); + } + + storeUserInLocalStorage(pubkey: string, displayName: string, picture: string) { + // hacky but store the data so its available in other places + localStorage.setItem(pubkey, displayName); + localStorage.setItem(`${pubkey}_img`, picture); + } + + getUserFromDB(pubkey: string): User | null { + let user: User | null = null; + this.dbService.getByIndex("users", "pubkey", pubkey) + .subscribe((result: DBUser | any) => { + if (result !== undefined) { + user = dbUserToUser(result) + } + }); + return user; + } + + + // nostr stuff + + async relayConnect() { + // Create a new SimplePool instance + const pool = new SimplePool(); + + // Get the relay URL from the signerService + const relayUrl = this.signerService.getRelay(); + + // Initialize the relay + const relay = pool.subscribeMany( + [relayUrl], + [], + { + onevent(event) { + console.log(`Event received from ${relayUrl}`, event); + }, + oneose() { + console.log(`Connection closed for ${relayUrl}`); + pool.close([relayUrl]); + } + } + ); + + // Handle connection and errors + relay[0].on('connect', () => { + console.log(`connected to ${relayUrl}`); + }); + + relay[0].on('error', (error) => { + console.error(`failed to connect to ${relayUrl}`, error); + }); + + // Return the relay instance + return relay[0]; + } + + + relays(): string[] { + return this.signerService.getRelays(); + } + + getPool(): SimplePool { + return new SimplePool() + } + + async poolList(filters: Filter[]): Promise { + // Create a new SimplePool instance + const pool = new SimplePool(); + + // Get the relay URLs + const relays = this.relays(); + + let events: Event[] = []; + + // Loop through each filter and query the pool + for (const filter of filters) { + const response = await pool.querySync(relays, filter); + events.push(...response); + } + + // Close the connections to the relays after querying + pool.close(relays); + + return events; + } + + async poolGet(filter: Filter): Promise { + const pool = this.getPool() + const relays = this.relays() + const response = await pool.get(relays, filter) + pool.close(relays) + return response + } + + async getKind0(filter: Filter, followingList: boolean = false): Promise { + // user metadata + filter.kinds = [0]; // force this regardless + const response = await this.poolList([filter]) + let users: User[] = []; + let content: any; // json parsed + let user: User; + response.forEach(e => { + try { + content = JSON.parse(e.content) + user = new User(content, e.created_at, e.pubkey) + if (followingList) { + user.setFollowing(followingList); + } + users.push(user) + this.storeUserInLocalStorage(e.pubkey, user.displayName, user.picture) + } catch (e) { + console.log(e); + } + }); + this.storeUsersInDB(users); + return users; + } + + async getUser(pubkey: string): Promise { + // user metadata + let user = null; + const filter: Filter = {kinds: [0], authors: [pubkey], limit: 1} + const response = await this.poolGet(filter) + if (!response) { + return null; + } + let kind0 = JSON.parse(response.content) + user = new User(kind0, response.created_at, response.pubkey); + this.storeUsersInDB([user]); + this.storeUserInLocalStorage(user.pubkey, user.displayName, user.picture); + return user; + } + + getSince(minutesAgo: number) { + let now = new Date() + return Math.floor(now.setMinutes(now.getMinutes() - minutesAgo) / 1000) + } + + getPostFromResponse(response: Event, repostingPubkey: string = "") { + let nip10Result = nip10.parse(response); + return new Post( + response.kind, + response.pubkey, + response.content, + response.id, + response.created_at, + nip10Result, + repostingPubkey + ); + } + + async getUserPosts(pubkey: string, since: number, until: number): Promise { + let kind1: Filter = { + kinds: [1], + authors: [pubkey], + limit: 100, + since: since, + until: until + } + let kind6: Filter = { + kinds: [6], + authors: [pubkey], + limit: 100, + since: since, + until: until + } + const response = await this.poolList([kind6, kind1]) + let posts: Post[] = []; + let repostIds: string[] = []; + response.forEach(e => { + if (e.kind === 1) { + posts.push(this.getPostFromResponse(e)); + } else { + repostIds.push(e.tags[0][1]); + } + }); + let repostFilter: Filter = {"ids": repostIds} + const r2 = await this.getKind1(repostFilter, pubkey); + posts.push(...r2); + posts.sort((a,b) => a.createdAt - b.createdAt).reverse(); + return posts; + } + + async getPostReplies() {} + + async getFollowingCount(pubkey: string): Promise { + // let filter: Filter = {kinds: [3], authors: [pubkey]} + // + // const response = await relay.count([filter]); + let following = await this.getFollowing(pubkey, 6969) + return following.length + } + + async getFollowerCount(pubkey: string): Promise { + // let filter: Filter = {kinds: [3], "#p": [pubkey]} + // + // const response = await relay.count([filter]); + // console.log(response) + // return 10; + let followers = await this.getFollowers(pubkey, 6969) + return followers.length + } + + async getFollowing(pubkey: string, limit: number = 100): Promise { + let filter: Filter = {kinds: [3], authors: [pubkey], limit: limit} + + const response = await this.poolGet(filter); + let following: string[] = [] + if (response) { + response.tags.forEach(tag => { + following.push(tag[1]); + }); + } + return following + } + + async getContactListEvent(pubkey: string) { + let filter: Filter = {kinds: [3], authors: [pubkey], limit: 1} + + const response = await this.poolGet(filter); + console.log(response) + return response + } + + async getMyLikes(): Promise { + let myLikesFilter: Filter = { + kinds: [7], "authors": [this.signerService.getPublicKey()] + } + let myLikes = await this.getKind7(myLikesFilter); + let myLikedNoteIds = []; + myLikes.forEach(like => { + try { + let tag = like.tags[like.tags.length - 2] + if (tag[0] == "e") { + let id = tag[1] + myLikedNoteIds.push(id); + } + + } catch { + console.log("err") + } + }); + return myLikedNoteIds + } + + async getEventLikes(eventPubkeys: string[]): Promise { + let myLikesFilter: Filter = { + kinds: [7], + "authors": [this.signerService.getPublicKey()], + "#p": eventPubkeys, + } + let myLikes = await this.getKind7(myLikesFilter); + let myLikedNoteIds = []; + myLikes.forEach(like => { + try { + let tag = like.tags[like.tags.length - 2] + if (tag[0] == "e") { + let id = tag[1] + myLikedNoteIds.push(id); + } + + } catch { + console.log("err") + } + }); + return myLikedNoteIds + } + + // count do count here as well ... + async getFollowers(pubkey: string, limit: number = 100): Promise { + let filter: Filter = {kinds: [3], "#p": [pubkey], limit: limit} + + const response = await this.poolList([filter]); + let followers: string[] = [] + if (response) { + response.forEach(r => { + followers.push(r.pubkey); + }); + } + return followers + } + + async getPost(id: string): Promise { + let filter: Filter = { + kinds: [1], + limit: 1, + ids: [id] + } + + const response = await this.poolGet(filter) + if (response) { + return this.getPostFromResponse(response); + } + return undefined; + } + + async getKind1(filter: Filter, repostingPubkey: string = ""): Promise{ + // text notes + filter.kinds = [1]; + + const response = await this.poolList([filter]) + let posts: Post[] = []; + const muteList: string[] = this.signerService.getMuteList(); + response.forEach(e => { + if (!muteList.includes(e.pubkey)) { + posts.push(this.getPostFromResponse(e, repostingPubkey)); + } else { + console.log("muted user found not including"); + } + }); + return posts; + } + + async searchUsers(searchTerm: string): Promise { + let filter: Filter = { + kinds: [0], + } + + const response = await this.poolList([filter]) + let users: User[] = []; + let content; + let user; + response.forEach(e => { + try { + content = JSON.parse(e.content); + user = new User(content, e.created_at, e.pubkey) + if (user.displayName.includes(searchTerm)) { + users.push(user) + } + if (user.pubkey.includes(searchTerm)) { + users.push(user) + } + if (user.npub.includes(searchTerm)) { + users.push(user) + } + this.storeUserInLocalStorage(e.pubkey, user.displayName, user.picture); + } catch (e) { + console.log(e); + } + }); + this.storeUsersInDB(users); + return users; + } + + async getKind1and6(filter: Filter): Promise{ + // text notes + filter.kinds = [1, 6]; + + const response = await this.poolList([filter]) + let posts: Post[] = []; + response.forEach(e => { + posts.push(this.getPostFromResponse(e)); + }); + return posts; + } + + async getPostAndReplies(id: string): Promise{ + let postFilter: Filter = { + ids: [id], kinds: [1], limit: 1 + } + let replyFilter: Filter = { + kinds: [1], "#e": [id] + } + + const response = await this.poolList([postFilter, replyFilter]) + let posts: Post[] = []; + response.forEach(e => { + posts.push(this.getPostFromResponse(e)); + }); + return posts; + } + + async getReplyCounts(filters: Filter[]): Promise{ + + const response = await this.poolList(filters) + let posts: Post[] = []; + response.forEach(e => { + posts.push(this.getPostFromResponse(e)); + }); + return posts; + } + + async getFeed(filters: Filter[]): Promise{ + // text notes + + const response = await this.poolList(filters) + let posts: Post[] = []; + response.forEach(e => { + posts.push(this.getPostFromResponse(e)); + }); + return posts; + } + + async getKind2(filter: Filter) { + // recommend server / relay + filter.kinds = [2]; + + return await this.poolList([filter]); + } + + async getKind3(filter: Filter): Promise { + // contact lists + filter.kinds = [3]; + + const response = await this.poolGet(filter); + let following: string[] = [] + if (response) { + response.tags.forEach(tag => { + following.push(tag[1]); + }); + } + return following; + } + + async getKind4(filter1: Filter, filter2: Filter): Promise { + + return await this.poolList([filter1, filter2]); + } + + async getKind4MessagesToMe(): Promise { + const filter: Filter = { + kinds: [4], + "#p": [this.signerService.getPublicKey()], + limit: 50 + } + const filter2: Filter = { + kinds: [4], + authors: [this.signerService.getPublicKey()], + limit: 50 + } + + return await this.poolList([filter, filter2]); + } + + async getPostLikeCount(filter: Filter): Promise { + + let likes = await this.poolList([filter]); + return likes.length + } + + async getKind11(limit: number) { + // server meta data (what types of NIPs a server is supporting) + + return await this.poolList([{kinds: [11], limit: limit}]); + } + + async getKind7(filter: Filter): Promise { + filter.kinds = [7]; + + return await this.poolList([filter]); + } + + async getZaps(filter: Filter) { + + const response = await this.poolList([filter]) + console.log(response); + let zaps: Zap[] = []; + response.forEach(e => { + + zaps.push(new Zap(e.id, e.kind, e.pubkey, e.created_at, e.sig, e.tags)); + }); + return zaps; + } + + async getContactList(pubkey: string) { + let filter: Filter = {kinds: [3], authors: [pubkey], limit: 1} + + const response = await this.poolGet(filter); + let following: string[] = [] + if (response) { + response.tags.forEach(tag => { + following.push(tag[1]); + }); + } + this.signerService.setFollowingList(following); + await this.getContactListUser(); + return following + } + + async getContactListUser() { + const filter: Filter = { + kinds: [0], + authors: this.signerService.getFollowingList() + } + await this.getKind0(filter, true); + } + + async getMuteList(pubkey: string): Promise { + const filter: Filter = { + "authors": [pubkey], + "kinds": [10000], + "limit": 1 + } + + const response = await this.poolGet(filter) + if (response) { + this.signerService.setMuteListFromTags(response.tags); + } else { + this.signerService.setMuteList([]); + } + } + + async addToMuteList(pubkey: string): Promise { + const muteList: string[] = this.signerService.getMuteList(); + + // Create the tags for the mute list + let tags: string[][] = [["p", pubkey]]; + for (let m of muteList) { + if (m) { + tags.push(["p", m]); + } + } + + // Create an unsigned event + const unsignedEvent = this.getUnsignedEvent(10000, tags, ""); + + // Get the private key + const privateKey = this.signerService.getPrivateKey(); + + // Check if privateKey is available and sign the event + let signedEvent: Event; + if (privateKey !== "") { + // Convert privateKey from hex string to Uint8Array + const privateKeyBytes = hexToBytes(privateKey); + // Finalize the event with signature + signedEvent = finalizeEvent(unsignedEvent, privateKeyBytes); + } else { + // Sign event with extension if no private key available + signedEvent = await this.signerService.signEventWithExtension(unsignedEvent); + } + + // Publish the signed event to the relays + this.publishEventToPool(signedEvent); + + // Update the mute list with the current public key + await this.getMuteList(this.signerService.getPublicKey()); + } + + async search(searchTerm: string): Promise { + let tags = searchTerm.split(' '); + // recommend server / relay + // let filter1: Filter = { + // kinds: [1], + // search: searchTerm, + // } + let filter3: Filter = { + kinds: [1], + "#t": tags, + limit: 50 + } + + const response = await this.poolList([filter3]); + let posts: Post[] = []; + response.forEach(e => { + const post = this.getPostFromResponse(e); + posts.push(post); + }); + return posts; + } + + getUnsignedEvent(kind: number, tags: string[][], content: string) { + const eventUnsigned: UnsignedEvent = { + kind: kind, + pubkey: this.signerService.getPublicKey(), + tags: tags, + content: content, + created_at: Math.floor(Date.now() / 1000), + } + return eventUnsigned + } + + getSignedEvent(eventUnsigned: UnsignedEvent, privateKey: string): Event { + // Convert the private key from hex string to Uint8Array + const privateKeyBytes = hexToBytes(privateKey); + + // finalizing and signing the event in one step + const signedEvent: Event = finalizeEvent(eventUnsigned, privateKeyBytes); + + return signedEvent; + } + + async getNotifications(): Promise { + // currently only gets zaps + const pubkey = this.signerService.getPublicKey(); + // check for replies + // check for zaps + // check for mentions? + // check for new followers + const zapFilter: Filter = {kinds: [9735], "#p": [pubkey]} + + const response = await this.poolList([zapFilter]) + let notifications: Zap[] = []; + response.forEach(e => { + notifications.push(new Zap(e.id, e.kind, e.pubkey, e.created_at, e.sig, e.tags)); + }); + notifications.sort((a,b) => a.createdAt - b.createdAt).reverse(); + this.storeNotificationsInDB(notifications); + return notifications; + } + + async sendEvent(event: Event) { + const relay = await this.relayConnect() + relay.publish(event) + relay.close(); + } + + async publishEventToPool(event: Event): Promise { + const relays: string[] = this.signerService.getRelays(); + const pool = new SimplePool() + let pubs = pool.publish(relays, event) + await Promise.all(pubs) + pool.close(relays) + } +} diff --git a/src/app/services/projects.service.ts b/src/app/services/projects.service.ts new file mode 100644 index 0000000..6ef4b30 --- /dev/null +++ b/src/app/services/projects.service.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { catchError } from 'rxjs/operators'; +import { of, Observable } from 'rxjs'; +import { IndexerService } from './indexer.service'; + +export interface Project { + founderKey: string; + nostrPubKey: string; + projectIdentifier: string; + createdOnBlock: number; + trxId: string; + totalInvestmentsCount: number; +} + +export interface ProjectStats { + investorCount: number; + amountInvested: number; + amountSpentSoFarByFounder: number; + amountInPenalties: number; + countInPenalties: number; +} + +@Injectable({ + providedIn: 'root', +}) +export class ProjectsService { + private offset = 0; + private limit = 50; + private totalProjects = 0; + private loading = false; + private projects: Project[] = []; + private noMoreProjects = false; + private totalProjectsFetched = false; + selectedNetwork: 'mainnet' | 'testnet' = 'testnet'; + + constructor( + private http: HttpClient, + private indexerService: IndexerService + + ) { + this.loadNetwork(); + } + + loadNetwork() { + this.selectedNetwork = this.indexerService.getNetwork(); + } + async fetchProjects(): Promise { + if (this.loading || this.noMoreProjects) { + return []; + } + + this.loading = true; + const indexerUrl = this.indexerService.getPrimaryIndexer(this.selectedNetwork); + const url = this.totalProjectsFetched + ? `${indexerUrl}api/query/Angor/projects?offset=${this.offset}&limit=${this.limit}` + : `${indexerUrl}api/query/Angor/projects?limit=${this.limit}`; + + console.log(`Fetching projects from URL: ${url}`); + + try { + const response = await this.http + .get(url, { observe: 'response' }) + .toPromise(); + + if (!this.totalProjectsFetched && response && response.headers) { + const paginationTotal = response.headers.get('pagination-total'); + this.totalProjects = paginationTotal ? +paginationTotal : 0; + console.log(`Total projects: ${this.totalProjects}`); + this.totalProjectsFetched = true; + + this.offset = Math.max(this.totalProjects - this.limit, 0); + } + + const newProjects = response?.body || []; + console.log('New projects received:', newProjects); + + if (!newProjects || newProjects.length === 0) { + this.noMoreProjects = true; + return []; + } else { + const uniqueNewProjects = newProjects.filter( + (newProject) => + !this.projects.some( + (existingProject) => + existingProject.projectIdentifier === newProject.projectIdentifier + ) + ); + + if (uniqueNewProjects.length > 0) { + this.projects = [...this.projects, ...uniqueNewProjects]; + console.log(`${uniqueNewProjects.length} new projects added`); + + this.offset = Math.max(this.offset - this.limit, 0); + return uniqueNewProjects; + } else { + this.noMoreProjects = true; + return []; + } + } + } catch (error) { + console.error('Error fetching projects:', error); + return []; + } finally { + this.loading = false; + } + } + + + fetchProjectStats(projectIdentifier: string): Observable { + const indexerUrl = this.indexerService.getPrimaryIndexer(this.selectedNetwork); + const url = `${indexerUrl}api/query/Angor/projects/${projectIdentifier}/stats`; + console.log(`Fetching project stats from URL: ${url}`); + + return this.http.get(url).pipe( + catchError((error) => { + console.error( + `Error fetching stats for project ${projectIdentifier}:`, + error + ); + return of({} as ProjectStats); + }) + ); + } + + fetchProjectDetails(projectIdentifier: string): Observable { + const indexerUrl = this.indexerService.getPrimaryIndexer(this.selectedNetwork); + const url = `${indexerUrl}api/query/Angor/projects/${projectIdentifier}`; + console.log(`Fetching project details from URL: ${url}`); + + return this.http.get(url).pipe( + catchError((error) => { + console.error( + `Error fetching details for project ${projectIdentifier}:`, + error + ); + return of({} as Project); + }) + ); + } + + getProjects(): Project[] { + return this.projects; + } + + resetProjects(): void { + this.projects = []; + this.noMoreProjects = false; + this.offset = 0; + this.totalProjectsFetched = false; + } +} diff --git a/src/app/services/relay.service.ts b/src/app/services/relay.service.ts new file mode 100644 index 0000000..e0a5a78 --- /dev/null +++ b/src/app/services/relay.service.ts @@ -0,0 +1,176 @@ +import { Injectable } from "@angular/core"; +import { Filter, NostrEvent, SimplePool } from "nostr-tools"; +import { Observable, Subject } from "rxjs"; + +@Injectable({ + providedIn: 'root', +}) +export class RelayService { + private pool: SimplePool; + private relays: { url: string, connected: boolean, retries: number, retryTimeout: any, ws?: WebSocket }[] = []; + private maxRetries = 10; + private retryDelay = 15000; + private eventSubject = new Subject(); + + constructor() { + this.pool = new SimplePool(); + this.relays = this.loadRelaysFromLocalStorage(); + this.connectToRelays(); + this.setupVisibilityChangeHandling(); + } + + private loadRelaysFromLocalStorage() { + const defaultRelays = [ + { url: 'wss://relay.angor.io', connected: false, retries: 0, retryTimeout: null, ws: undefined }, + { url: 'wss://relay2.angor.io', connected: false, retries: 0, retryTimeout: null, ws: undefined }, + ]; + + const storedRelays = JSON.parse(localStorage.getItem('nostrRelays') || '[]').map((relay: any) => ({ + ...relay, + connected: false, + retries: 0, + retryTimeout: null, + ws: undefined, + })); + return [...defaultRelays, ...storedRelays]; + } + + private connectToRelay(relay: { url: string, connected: boolean, retries: number, retryTimeout: any, ws?: WebSocket }) { + if (relay.connected) { + return; + } + + relay.ws = new WebSocket(relay.url); + + relay.ws.onopen = () => { + relay.connected = true; + relay.retries = 0; + clearTimeout(relay.retryTimeout); + console.log(`Connected to relay: ${relay.url}`); + this.saveRelaysToLocalStorage(); + }; + + relay.ws.onerror = (error) => { + console.error(`Failed to connect to relay: ${relay.url}`, error); + this.handleRelayError(relay); + }; + + relay.ws.onclose = () => { + relay.connected = false; + console.log(`Disconnected from relay: ${relay.url}`); + this.handleRelayError(relay); + }; + + relay.ws.onmessage = (message) => { + try { + const dataStr = typeof message.data === 'string' ? message.data : message.data.toString('utf-8'); + const parsedData = JSON.parse(dataStr); + this.eventSubject.next(parsedData); + } catch (error) { + console.error('Error parsing WebSocket message:', error); + } + }; + } + + private handleRelayError(relay: { url: string, connected: boolean, retries: number, retryTimeout: any, ws?: WebSocket }) { + if (relay.retries >= this.maxRetries) { + console.error(`Max retries reached for relay: ${relay.url}. No further attempts will be made.`); + return; + } + + const retryInterval = this.retryDelay * relay.retries; + relay.retries++; + + relay.retryTimeout = setTimeout(() => { + this.connectToRelay(relay); + console.log(`Retrying connection to relay: ${relay.url} (Attempt ${relay.retries})`); + }, retryInterval); + } + + public connectToRelays() { + this.relays.forEach((relay) => this.connectToRelay(relay)); + } + + public async ensureConnectedRelays(): Promise { + this.connectToRelays(); + + return new Promise((resolve) => { + const checkConnection = () => { + if (this.getConnectedRelays().length > 0) { + resolve(); + } else { + setTimeout(checkConnection, 1000); + } + }; + checkConnection(); + }); + } + + private setupVisibilityChangeHandling() { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + this.connectToRelays(); + } + }); + + window.addEventListener('beforeunload', () => { + this.relays.forEach(relay => { + if (relay.ws) { + relay.ws.close(); + } + }); + }); + } + + public getConnectedRelays(): string[] { + return this.relays.filter((relay) => relay.connected).map((relay) => relay.url); + } + + public saveRelaysToLocalStorage(): void { + const customRelays = this.relays.filter( + (relay) => !['wss://relay.angor.io', 'wss://relay2.angor.io'].includes(relay.url) + ); + localStorage.setItem('nostrRelays', JSON.stringify(customRelays)); + } + + public getEventStream(): Observable { + return this.eventSubject.asObservable(); + } + + public addRelay(url: string): void { + if (!this.relays.some(relay => relay.url === url)) { + const newRelay = { url, connected: false, retries: 0, retryTimeout: null, ws: undefined }; + this.relays.push(newRelay); + this.connectToRelay(newRelay); + this.saveRelaysToLocalStorage(); + } + } + + public removeRelay(url: string): void { + this.relays = this.relays.filter(relay => relay.url !== url); + this.saveRelaysToLocalStorage(); + } + + public removeAllCustomRelays(): void { + const defaultRelays = ['wss://relay.angor.io', 'wss://relay2.angor.io']; + this.relays = this.relays.filter(relay => defaultRelays.includes(relay.url)); + this.saveRelaysToLocalStorage(); + } + + public subscribeToFilter(filter: Filter): void { + const connectedRelays = this.getConnectedRelays(); + this.pool.subscribeMany(connectedRelays, [filter], { + onevent: (event: NostrEvent) => { + this.eventSubject.next(event); + }, + }); + } + + public getPool(): SimplePool { + return this.pool; + } + + public getRelays(): { url: string, connected: boolean, ws?: WebSocket }[] { + return this.relays; + } +} diff --git a/src/app/services/security.service.ts b/src/app/services/security.service.ts new file mode 100644 index 0000000..eddffde --- /dev/null +++ b/src/app/services/security.service.ts @@ -0,0 +1,90 @@ +import { Injectable } from '@angular/core'; +import { base64 } from '@scure/base'; + +@Injectable({ + providedIn: 'root', +}) +export class SecurityService { + private encoder = new TextEncoder(); + private decoder = new TextDecoder(); + + private async getPasswordKey(password: string): Promise { + return window.crypto.subtle.importKey( + 'raw', + this.encoder.encode(password), + 'PBKDF2', + false, + ['deriveKey'] + ); + } + + private async deriveKey( + passwordKey: CryptoKey, + salt: Uint8Array, + keyUsage: KeyUsage[] + ): Promise { + return window.crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt: salt, + iterations: 250000, + hash: 'SHA-256', + }, + passwordKey, + { name: 'AES-GCM', length: 256 }, + false, + keyUsage + ); + } + + async encryptData(secretData: string, password: string): Promise { + try { + const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const passwordKey = await this.getPasswordKey(password); + const aesKey = await this.deriveKey(passwordKey, salt, ['encrypt']); + + const encryptedContent = new Uint8Array( + await window.crypto.subtle.encrypt( + { name: 'AES-GCM', iv: iv }, + aesKey, + this.encoder.encode(secretData) + ) + ); + + const encryptedData = new Uint8Array(salt.length + iv.length + encryptedContent.length); + encryptedData.set(salt, 0); + encryptedData.set(iv, salt.length); + encryptedData.set(encryptedContent, salt.length + iv.length); + + return base64.encode(encryptedData); + } catch (e) { + console.error('Encryption failed:', e); + throw new Error('Failed to encrypt data.'); + } + } + + async decryptData(encryptedData: string, password: string): Promise { + try { + const encryptedDataBuff = base64.decode(encryptedData); + + const salt = encryptedDataBuff.slice(0, 16); + const iv = encryptedDataBuff.slice(16, 28); + const data = encryptedDataBuff.slice(28); + + const passwordKey = await this.getPasswordKey(password); + const aesKey = await this.deriveKey(passwordKey, salt, ['decrypt']); + + const decryptedContent = await window.crypto.subtle.decrypt( + { name: 'AES-GCM', iv: iv }, + aesKey, + data + ); + + return this.decoder.decode(decryptedContent); + } catch (e) { + console.error('Decryption failed:', e); + throw new Error('Failed to decrypt data.'); + } + } +} diff --git a/src/app/services/signer.service.ts b/src/app/services/signer.service.ts new file mode 100644 index 0000000..66e4a3c --- /dev/null +++ b/src/app/services/signer.service.ts @@ -0,0 +1,297 @@ +import { Injectable } from '@angular/core'; +import { UnsignedEvent, nip19, getPublicKey, nip04, Event } from 'nostr-tools'; + + +@Injectable({ + providedIn: 'root' +}) +export class SignerService { + + localStoragePrivateKeyName: string = "privateKey"; + localStoragePublicKeyName: string = "publicKey"; + + constructor() { } + + clearKeys() { + localStorage.removeItem("nostrWalletConnectURI"); + localStorage.removeItem("muteList"); + localStorage.removeItem("currentChip"); + localStorage.removeItem("following"); + localStorage.removeItem(`${this.getPublicKey()}_img`); + localStorage.removeItem(this.localStoragePrivateKeyName); + localStorage.removeItem(this.localStoragePublicKeyName); + console.log("data cleared") + console.log(localStorage.getItem("nostrWalletConnect")) + } + + getUsername(pubkey: string) { + // can take a pubkey or npub + if (pubkey.startsWith("npub")) { + pubkey = nip19.decode(pubkey).data.toString() + } + return `@${(localStorage.getItem(`${pubkey}`) || nip19.npubEncode(pubkey))}` + } + + npub() { + let pubkey = this.getPublicKey(); + return nip19.npubEncode(pubkey); + } + + nsec() { + if (this.usingPrivateKey()) { + let privateKey = this.getPrivateKey(); + const privateKeyUint8Array = Uint8Array.from(Buffer.from(privateKey, 'hex')); + return nip19.nsecEncode(privateKeyUint8Array); + } + return ""; + } + + pubkey(npub: string) { + return nip19.decode(npub).data.toString(); + } + + encodeNoteAsEvent(note: string): string { + let decodedNote = nip19.decode(note).data.toString() + let eventP: nip19.EventPointer = {id: decodedNote} + return nip19.neventEncode(eventP); + } + + getPublicKey() { + return localStorage.getItem(this.localStoragePublicKeyName) || ""; + } + + getPrivateKey() { + return localStorage.getItem(this.localStoragePrivateKeyName) || ""; + } + + getLoggedInUserImage() { + // gets from local storage if we have it + return localStorage.getItem(`${this.getPublicKey()}_img`) || ""; + } + + setLoggedInUserImage(url: string) { + // sets user image link in local storage + return localStorage.setItem(`${this.getPublicKey()}_img`, url); + } + + usingPrivateKey() { + if (localStorage.getItem(this.localStoragePrivateKeyName)) { + return true; + } + return false; + } + + getFollowingList() { + const followingRaw = localStorage.getItem("following"); + if (followingRaw === null || followingRaw === "") { + return []; + } + let following = (followingRaw).split(','); + return following.filter(value => /[a-f0-9]{64}/.test(value)); + } + + getMuteList() { + return (localStorage.getItem("muteList") || "").split(','); + } + + setMuteListFromTags(tags: string[][]): void { + let muteList: string[] = [] + tags.forEach(t => { + muteList.push(t[1]); + }) + this.setMuteList(muteList); + } + + setMuteList(muteList: string[]) { + if (muteList.length === 0) { + localStorage.setItem("muteList", ""); + } else { + let muteSet = Array.from(new Set(muteList)); + localStorage.setItem("muteList", muteSet.filter(s => s).join(',')); + } + } + + getRelay(): string { + return localStorage.getItem("relay") || "wss://relay.damus.io/"; + } + + getRelays(): string[] { + const relaysString = localStorage.getItem("relays") || ""; + let relays = relaysString.split(",").map((item) => { + return item.trim(); + }); + if (relays.length === 0 || relays[0] === "") { + relays = [ + "wss://relay.damus.io/", + "wss://nostr.fmt.wiz.biz/", + "wss://relay.nostr.band/", + "wss://relay.snort.social/", + "wss://nostr.mom", + "wss://relayable.org", + "wss://purplepag.es", + ] + } + return relays + } + + setRelay(relay: string): void { + localStorage.setItem("relay", relay); + } + + setRelays(relays: string[]): void { + localStorage.setItem("relays", relays.join(",")); + } + + getDefaultZap() { + return localStorage.getItem("defaultZap") || "5"; + } + + setDefaultZap(defaultZap: string) { + localStorage.setItem("defaultZap", defaultZap) + } + + setNostrWalletConnectURI(uri: string) { + localStorage.setItem("nostrWalletConnectURI", uri); + } + + getNostrWalletConnectURI() { + const x = localStorage.getItem("nostrWalletConnectURI") || ""; + if (x === "") { + return null; + } + return x; + } + + getFollowingListAsTags(): string[][] { + let following = this.getFollowingList(); + let tags: string[][] = []; + following.forEach(f => { + tags.push(["p", f, "wss://relay.damus.io/", localStorage.getItem(`${f}`) || ""]); + }); + return tags; + } + + setFollowingListFromTags(tags: string[][]): void { + let following: string[] = [] + tags.forEach(t => { + following.push(t[1]); + }) + this.setFollowingList(following); + } + + setFollowingList(following: string[]) { + let followingSet = Array.from(new Set(following)); + let newFollowingList = followingSet.filter(s => s).join(',') + localStorage.setItem("following", newFollowingList); + } + + savePublicKeyToSession(publicKey: string) { + localStorage.setItem(this.localStoragePublicKeyName, publicKey); + } + + savePrivateKeyToSession(privateKey: string) { + localStorage.setItem(this.localStoragePrivateKeyName, privateKey); + } + + setPublicKeyFromExtension(publicKey: string) { + this.savePublicKeyToSession(publicKey); + } + + setBlurImagesIfNotFollowing(blur: boolean) { + if (blur) { + localStorage.setItem("blur", "true"); + } else { + localStorage.setItem("blur", "false"); + } + } + + getBlurImagesIfNotFollowing() { + const blur = localStorage.getItem("blur") || "true"; + if (blur === "false") { + return false; + } + return true; + } + + handleLoginWithNsec(nsec: string) { + let privateKey: string; + try { + privateKey = nip19.decode(nsec).data.toString(); + } catch (e) { + return false; + } + + // Assuming the private key is stored in hexadecimal format + const privateKeyUint8Array = Uint8Array.from(Buffer.from(privateKey, 'hex')); + let pubkey = getPublicKey(privateKeyUint8Array); + this.savePrivateKeyToSession(privateKey); + this.savePublicKeyToSession(pubkey); + console.log(this.getPublicKey()) + console.log(this.getPrivateKey()) + return true; + } + + usingNostrBrowserExtension() { + if (this.usingPrivateKey()) { + return false; + } + const gt = globalThis as any; + if (gt.nostr) { + return true; + } + return false; + } + + async handleLoginWithExtension(): Promise { + const gt = globalThis as any; + if (gt.nostr) { + const pubkey = await gt.nostr.getPublicKey().catch((e) => { + console.log(e); + return ""; + }); + if (pubkey === "") { + return false; + } + this.setPublicKeyFromExtension(pubkey); + return true; + } + return false; + } + + async signEventWithExtension(unsignedEvent: UnsignedEvent): Promise { + const gt = globalThis as any; + if (gt.nostr) { + const signedEvent = await gt.nostr.signEvent(unsignedEvent) + return signedEvent; + } else { + throw new Error("Tried to sign event with extension but failed"); + } + } + + async signDMWithExtension(pubkey: string, content: string): Promise { + const gt = globalThis as any; + if (gt.nostr && gt.nostr.nip04?.encrypt) { + return await gt.nostr.nip04.encrypt(pubkey, content) + } + throw new Error("Failed to Sign with extension"); + } + + async decryptDMWithExtension(pubkey: string, ciphertext: string): Promise { + const gt = globalThis as any; + if (gt.nostr && gt.nostr.nip04?.decrypt) { + const decryptedContent = await gt.nostr.nip04.decrypt(pubkey, ciphertext) + .catch((error: any) => { + return "*Failed to Decrypted Content*" + }); + return decryptedContent; + } + return "Attempted Nostr Window decryption and failed." + } + + async decryptWithPrivateKey(pubkey: string, ciphertext: string): Promise { + let privateKey = this.getPrivateKey() + return await nip04.decrypt(privateKey, pubkey, ciphertext).catch((error) => { + return "*Failed to Decrypted Content*"; + }); + } +} diff --git a/src/app/services/state.service.ts b/src/app/services/state.service.ts new file mode 100644 index 0000000..8522d8c --- /dev/null +++ b/src/app/services/state.service.ts @@ -0,0 +1,83 @@ +import { Injectable } from '@angular/core'; +import { NostrService } from './nostr.service'; + +@Injectable({ + providedIn: 'root' +}) +export class StateService { + private projects: any[] = []; + private metadataCache: Map = new Map(); + + constructor(private nostrService: NostrService) {} + + + async setProjects(projects: any[]): Promise { + this.projects = projects; + this.updateMetadataInBackground(); + } + + + getProjects(): any[] { + return this.projects; + } + + + hasProjects(): boolean { + return this.projects.length > 0; + } + + + async updateProjectActivity(project: any): Promise { + const index = this.projects.findIndex(p => p.nostrPubKey === project.nostrPubKey); + + if (index > -1) { + this.projects[index] = project; + } else { + this.projects.push(project); + } + + this.projects.sort((a, b) => b.lastActivity - a.lastActivity); + await this.updateMetadataForProject(project); // Ensure metadata is updated for the project + } + + + private updateMetadataInBackground(): void { + const batchSize = 5; + + for (let i = 0; i < this.projects.length; i += batchSize) { + const batch = this.projects.slice(i, i + batchSize); + batch.forEach(project => this.updateMetadataForProject(project)); + } + } + + private async updateMetadataForProject(project: any): Promise { + if (this.metadataCache.has(project.nostrPubKey)) { + this.applyMetadata(project, this.metadataCache.get(project.nostrPubKey)); + return; + } + + try { + const metadata = await this.nostrService.fetchMetadata(project.nostrPubKey); + + if (metadata) { + this.applyMetadata(project, metadata); + this.metadataCache.set(project.nostrPubKey, metadata); // Cache the metadata + } else { + console.warn(`Metadata is null for project ${project.nostrPubKey}.`); + } + } catch (error) { + console.error(`Error fetching metadata for project ${project.nostrPubKey}:`, error); + } + } + + +private applyMetadata(project: any, metadata: any): void { + if (metadata && typeof metadata === 'object') { + project.displayName = metadata.name || project.displayName; + project.picture = metadata.picture || project.picture; + } else { + console.warn(`Metadata for project ${project.nostrPubKey} is invalid or null.`); + } +} + +} diff --git a/src/app/shared/emoji-picker/emoji-picker.component.css b/src/app/shared/emoji-picker/emoji-picker.component.css new file mode 100644 index 0000000..52a8afc --- /dev/null +++ b/src/app/shared/emoji-picker/emoji-picker.component.css @@ -0,0 +1,83 @@ +.emoji-picker { + position: absolute; + bottom: 60px; + right: 0; + background: var(--card-background); + border: 1px solid rgba(0, 0, 0, 0.1); + border-radius: 10px; + padding: 15px; + max-width: 360px; + width: 100%; + max-height: 400px; + overflow-y: auto; + z-index: 1000; + box-shadow: 0px 8px 15px rgba(0, 0, 0, 0.2); + animation: fadeIn 0.3s ease; + overflow-x: hidden; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.emoji-search-bar { + padding: 10px; + margin-bottom: 15px; + border-radius: 8px; + border: 1px solid rgba(0, 0, 0, 0.1); + width: 100%; + font-size: 16px; + background-color: var(--input-background); + box-shadow: inset 0px 2px 5px rgba(0, 0, 0, 0.1); +} + +.emoji-category h3 { + margin: 10px 0; + font-size: 18px; + font-weight: bold; + color: var(--text-color); + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + padding-bottom: 5px; +} + +.emoji-container { + display: flex; + flex-wrap: wrap; + gap: 10px; + justify-content: flex-start; +} + +.emoji { + padding: 8px; + font-size: 24px; + cursor: pointer; + display: inline-block; + user-select: none; + border-radius: 50%; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.emoji:hover { + transform: scale(1.2); + box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); +} + +.emoji:active { + transform: scale(1.1); + box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.2); +} + +.emoji-scroll-viewport { + height: 300px; + width: 100%; + padding-right: 5px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); +} + diff --git a/src/app/shared/emoji-picker/emoji-picker.component.html b/src/app/shared/emoji-picker/emoji-picker.component.html new file mode 100644 index 0000000..5c0d8b0 --- /dev/null +++ b/src/app/shared/emoji-picker/emoji-picker.component.html @@ -0,0 +1,27 @@ + +
+ + + + +
+

{{ category.key }}

+
+
+ + {{ emoji.emoji }} + +
+
+
+
+
diff --git a/src/app/shared/emoji-picker/emoji-picker.component.ts b/src/app/shared/emoji-picker/emoji-picker.component.ts new file mode 100644 index 0000000..5a436eb --- /dev/null +++ b/src/app/shared/emoji-picker/emoji-picker.component.ts @@ -0,0 +1,79 @@ +import { Component, Output, EventEmitter, OnInit } from '@angular/core'; +import { EmojiService } from '../../services/emoji.service'; +import { debounceTime, distinctUntilChanged, Subject } from 'rxjs'; + +@Component({ + selector: 'app-emoji-picker', + templateUrl: './emoji-picker.component.html', + styleUrls: ['./emoji-picker.component.css'], +}) +export class EmojiPickerComponent implements OnInit { + @Output() emojiSelected = new EventEmitter(); + public emojiSearch: string = ''; + public emojiCategories: { [key: string]: { name: string, emoji: string }[] } = {}; + public loading: boolean = true; + private searchSubject = new Subject(); + + constructor(private emojiService: EmojiService) {} + + ngOnInit(): void { + this.emojiService.getEmojis().subscribe( + (data) => { + this.emojiCategories = this.formatEmojis(data); + this.loading = false; + }, + (error) => { + console.error('Error loading emoji data', error); + this.loading = false; + } + ); + + // Subscribe to search input changes with debounce + this.searchSubject.pipe( + debounceTime(300), // Wait 300ms after the last event before emitting + distinctUntilChanged() // Only emit when the current value is different than the last + ).subscribe((searchTerm) => { + this.emojiSearch = searchTerm; + }); + } + + formatEmojis(data: any): { [key: string]: { name: string, emoji: string }[] } { + const formattedCategories: { [key: string]: { name: string, emoji: string }[] } = {}; + + Object.keys(data).forEach((categoryName) => { + formattedCategories[categoryName] = data[categoryName].map((emojiObj: any) => ({ + name: emojiObj.name, + emoji: emojiObj.emoji, + })); + }); + + return formattedCategories; + } + + get filteredEmojiCategories(): { [key: string]: { name: string, emoji: string }[] } { + if (!this.emojiSearch) return this.emojiCategories; + + const search = this.emojiSearch.toLowerCase(); + const filteredCategories: { [key: string]: { name: string, emoji: string }[] } = {}; + + Object.keys(this.emojiCategories).forEach((category) => { + const filteredEmojis = this.emojiCategories[category].filter((emojiObj) => + emojiObj.name.toLowerCase().includes(search) + ); + if (filteredEmojis.length > 0) { + filteredCategories[category] = filteredEmojis; + } + }); + + return filteredCategories; + } + + selectEmoji(emoji: string): void { + this.emojiSelected.emit(emoji); + this.emojiSearch = ''; + } + + onSearchChange(searchValue: string): void { + this.searchSubject.next(searchValue); // Emit search value to debounce + } +} diff --git a/src/app/shared/password-dialog/password-dialog.component.css b/src/app/shared/password-dialog/password-dialog.component.css new file mode 100644 index 0000000..11f5960 --- /dev/null +++ b/src/app/shared/password-dialog/password-dialog.component.css @@ -0,0 +1,61 @@ + +.dialog-content { + display: flex; + align-items: center; +} + +.password-form-field { + width: 100%; + margin-bottom: 20px; +} + +.password-input { + margin: 10px; + font-size: 16px; + border-radius: 4px; +} + +.dialog-actions { + display: flex; + justify-content: flex-end; + gap: 15px; + margin: 10px; +} + +.btn-cancel { + background-color: transparent; + color: #777; + font-weight: bold; + text-transform: uppercase; + border: 2px solid #ccc; + padding: 8px 20px; + border-radius: 5px; + transition: all 0.3s ease; +} + +.btn-cancel:hover { + background-color: #f5f5f5; + border-color: #bbb; + color: #555; +} + +.btn-confirm { + background-color: #009fb5; + color: white; + font-weight: bold; + text-transform: uppercase; + padding: 8px 20px; + border-radius: 5px; + transition: all 0.3s ease; +} + +.btn-confirm:hover { + background-color: #007a8c; +} + +button[mat-button], button[mat-raised-button] { + min-width: 100px; + font-size: 14px; + font-weight: bold; +} + diff --git a/src/app/shared/password-dialog/password-dialog.component.html b/src/app/shared/password-dialog/password-dialog.component.html new file mode 100644 index 0000000..07d6167 --- /dev/null +++ b/src/app/shared/password-dialog/password-dialog.component.html @@ -0,0 +1,12 @@ +
+

{{ data.message }}

+
+ +
+
+ + +
+
diff --git a/src/app/shared/password-dialog/password-dialog.component.ts b/src/app/shared/password-dialog/password-dialog.component.ts new file mode 100644 index 0000000..18c6a62 --- /dev/null +++ b/src/app/shared/password-dialog/password-dialog.component.ts @@ -0,0 +1,45 @@ +import { TextFieldModule } from '@angular/cdk/text-field'; +import { NgClass } from '@angular/common'; +import { Component, Inject } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatOptionModule } from '@angular/material/core'; +import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog'; +import { MatFormFieldModule } from '@angular/material/form-field'; +import { MatIconModule } from '@angular/material/icon'; +import { MatInputModule } from '@angular/material/input'; + +@Component({ + selector: 'app-password-dialog', + templateUrl: './password-dialog.component.html', + styleUrls: ['./password-dialog.component.css'], + standalone: true, + imports: [ + MatIconModule, + FormsModule, + MatFormFieldModule, + NgClass, + MatInputModule, + TextFieldModule, + ReactiveFormsModule, + MatButtonModule, + MatOptionModule, + MatDialogModule + ], +}) +export class PasswordDialogComponent { + password: string = ''; + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any + ) {} + + onNoClick(): void { + this.dialogRef.close(); + } + + onOkClick(): void { + this.dialogRef.close(this.password); + } +} diff --git a/src/app/shared/truncate.pipe.ts b/src/app/shared/truncate.pipe.ts new file mode 100644 index 0000000..4ada2d6 --- /dev/null +++ b/src/app/shared/truncate.pipe.ts @@ -0,0 +1,13 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'truncate' +}) +export class TruncatePipe implements PipeTransform { + + transform(value: string, limit: number): string { + if (!value) return ''; + if (value.length <= limit) return value; + return value.substring(0, limit) + '...'; + } +} diff --git a/src/app/types/gif.ts b/src/app/types/gif.ts new file mode 100644 index 0000000..265dfed --- /dev/null +++ b/src/app/types/gif.ts @@ -0,0 +1,36 @@ + +export interface Gif { + preview: string; + size: number; + url: string; +} + +export interface TenorGif { + gif: Gif; + nanogif: Gif; +} + +export interface TenorGifResponse { + bg_color: string; + composite?: string + content_description: string; + content_rating: string; + created: number; + flags? : string[]; + h1_title: string; + hasaudio: boolean; + hascaption: boolean; + id: string; + itemurl: string; + media: TenorGif[]; + shares: number; + source_id: string; + tags?: string[]; + title: string; + url: string; +} + +export interface TenorResponse { + results: TenorGifResponse[]; + next: string; +} diff --git a/src/app/types/nostr.ts b/src/app/types/nostr.ts new file mode 100644 index 0000000..d1a11e4 --- /dev/null +++ b/src/app/types/nostr.ts @@ -0,0 +1,20 @@ +import { UnsignedEvent, Event } from "nostr-tools" + +export interface NostrWindow { + getPublicKey: () => Promise + signEvent: (event: UnsignedEvent) => Promise + nip04?: { + encrypt?: (pubkey: string, plaintext: string) => Promise + decrypt?: (pubkey: string, ciphertext: string) => Promise + } +} + + +export interface NIP05Names { + [key: string]: string; +} + +export interface NIP05 { + names: NIP05Names; + relays?: {}; +} diff --git a/src/app/types/notification.ts b/src/app/types/notification.ts new file mode 100644 index 0000000..f67c6ff --- /dev/null +++ b/src/app/types/notification.ts @@ -0,0 +1,5 @@ +export class NostrNotification { + pubkey!: string; + eventId!: string; + amount!: string; +} diff --git a/src/app/types/post.ts b/src/app/types/post.ts new file mode 100644 index 0000000..7d7bb67 --- /dev/null +++ b/src/app/types/post.ts @@ -0,0 +1,534 @@ +import { NIP10Result } from 'nostr-tools/nip10'; +import { Event, nip19, nip10 } from 'nostr-tools'; +import { decode } from "@gandlaf21/bolt11-decode"; + import dayjs from 'dayjs'; +import * as relativeTime from 'dayjs/plugin/relativeTime'; +import { humantime } from 'app/utils'; + +export interface TextWrap { + text: string; + cssClass?: string; + addLink?: string; + npub?: string; + nevent?: string; + hashtag?: string; +} + +export interface LightningResponse { + allowsNostr?: boolean; + nostrPubkey?: string; + callback?: string; // The URL from LN SERVICE which will accept the pay request parameters + commentAllowed?: number; + maxSendable?: number; // Max millisatoshi amount LN SERVICE is willing to receive + minSendable?: number; // Min millisatoshi amount LN SERVICE is willing to receive, can not be less than 1 or more than `maxSendable` + metadata?: string; // Metadata json which must be presented as raw string here, this is required to pass signature verification at a later step + tag?: string; + status?: string; + reason?: string; +} + +export interface LightningInvoice { + pr: string; + routes?: string[]; +} + +export interface ZapRequest { + kind: number; + content: string; + tags: string[][]; + pubkey: string; + created_at: number; + id: string; + sig: string; +} + +export class Zap { + id: string; + kind: number; + walletPubkey: string; + walletNpub: string; + createdAt: number; + date: Date; + sig: string; + tags: string[][]; + username: string = ""; + picture: string = ""; + receiverPubKey: string; + receiverNpub: string; + receiverEventId: string; + senderPubkey: string = ""; + senderNpub: string = ""; + senderMessage: string = ""; + bolt11: string; + preImage: string; + description: Event | null; + fromNow: string = ""; + content: string = ""; + satAmount: number; + constructor(id: string, kind: number, pubkey: string, created_at: number, sig: string, tags: string[][]) { + this.id = id; + this.kind = kind; + this.walletPubkey = pubkey; + this.setUsername(this.walletPubkey); + this.setPicture(this.walletPubkey); + this.walletNpub = nip19.npubEncode(this.walletPubkey); + this.sig = sig; + this.tags = tags; + this.receiverPubKey = this.getUserPubkey(); + this.receiverNpub = nip19.npubEncode(this.receiverPubKey); + this.receiverEventId = this.getEventId(); + this.bolt11 = this.getBolt11(); + this.satAmount = this.getBolt11Amount(); + this.preImage = this.getPreImage(); + this.description = this.getDescription(); + this.setSender(); + this.createdAt = created_at; + this.date = new Date(this.createdAt*1000); + this.setFromNow(); + this.setContent(); + } + + getUserPubkey() { + const p: string = "p"; + for (let tag of this.tags) { + if (tag[0] === p) { + return tag[1]; + } + } + return ""; + } + + getEventId() { + const e: string = "e"; + for (let tag of this.tags) { + if (tag[0] === e) { + return tag[1]; + } + } + return ""; + } + + getBolt11() { + const bolt: string = "bolt11"; + for (let tag of this.tags) { + if (tag[0] === bolt) { + return tag[1]; + } + } + return ""; + } + + getPreImage() { + const pi: string = "preimage"; + for (let tag of this.tags) { + if (tag[0] === pi) { + return tag[1]; + } + } + return ""; + } + + getDescription(): Event | null { + const desc: string = "description"; + for (let tag of this.tags) { + if (tag[0] === desc) { + try { + return JSON.parse(tag[1]) as Event; + } catch (e) { + console.log(`couldn't parse zap receipt description: ${tag}`); + return null; + } + } + } + return null; + } + + getBolt11Amount() { + if (this.bolt11) { + const decodedInvoice = decode(this.bolt11); + for (let s of decodedInvoice.sections) { + if (s.name === "amount") { + return Number(s.value)/1000; + } + } + } + return 0; + } + + setContent() { + if (this.description) { + let content = "
" + content = content + `
${this.satAmount} sats ZAP! ${humantime(this.createdAt)}
`; + content = content + `

To: nostr:${this.receiverNpub}

From: nostr:${this.senderNpub}

`; + if (this.receiverEventId) { + content = content + `

Note: nostr:${nip19.neventEncode({id: this.receiverEventId})}

`; + } + if (this.senderMessage) { + content = content + `

Message: ${this.senderMessage}

`; + } + content = content + "
"; + let nip10Result = nip10.parse(this.description); + this.content = new Content(this.kind, content, nip10Result).getParsedContent() + } else { + this.content = "

Anon Zap

"; + } + } + + setSender() { + if (this.description) { + this.senderMessage = this.description.content; + this.senderPubkey = this.description.pubkey; + this.senderNpub = nip19.npubEncode(this.senderPubkey); + } + } + + setFromNow(): void { + this.fromNow = dayjs(this.date).fromNow() + } + + setUsername(pubkey: string): void { + this.username = localStorage.getItem(`${pubkey}_name`) || pubkey; // TODO + } + + setPicture(pubkey: string): void { + this.picture = localStorage.getItem(`${pubkey}_img`) || "https://axiumradonmitigations.com/wp-content/uploads/2015/01/icon-user-default.png"; + } +} + +export function isYoutubeVideo(url: string): boolean { + var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/ig; + return (url.match(p)) ? true : false; +} + +export class Content { + kind: number; + content: string; + nip10Result: NIP10Result; + addHash: boolean; + constructor(kind: number, content: string, nip10Result: NIP10Result, addHash: boolean = true) { + this.kind = kind; + this.content = content; + this.nip10Result = nip10Result; + this.addHash = addHash + } + + getParsedContent(ignoreNIP10: boolean = false): string { + if (this.kind === 6) { + this.content = this.reposted(); + } + if (!ignoreNIP10) { + this.content = this.nip08Replace(this.content); + } + this.content = this.parseLightningInvoice(this.content); + this.content = this.hashtagContent(this.content); + this.content = this.cashtagContent(this.content); + this.content = this.linkify(this.content); + this.content = this.replaceNostrThing(this.content); + return this.content; + } + + parseCreateNote() { + this.content = this.hashtagContent(this.content); + this.content = this.cashtagContent(this.content); + this.content = this.styleUsername(this.content); + return this.content; + } + + getNevent(ep: nip19.EventPointer): string { + return nip19.neventEncode(ep); + } + + hasEventPointer(content: string): boolean { + if (content.includes("nostr:")) { + return true; + } + return false; + } + + ellipsis(value: string): string { + if (value.length < 40) return value; + let section: number = value.length / 8; + let finalSection: number = value.length - section; + return value.substring(0, section) + ":" + value.substring(finalSection) + } + + reposted(): string { + if (this.nip10Result.root) { + return `nostr:${this.getNevent(this.nip10Result.root)}`; + } + return ""; + } + + wrapTextInSpan(textWrap: TextWrap): string { + if (textWrap.cssClass === undefined) { + textWrap.cssClass = "hashtag" + } + if (textWrap.npub) { + return `${textWrap.text}`; + } else if (textWrap.nevent) { + return `${textWrap.text}`; + } else if (textWrap.hashtag) { + return `${textWrap.text}`; + } else if (this.addHash && textWrap.addLink) { + // this fixes an issue in user about not redirecting properly + textWrap.addLink = textWrap.addLink.replace('href="', 'href="/#/') + return `${textWrap.text}` + } + return `${textWrap.text}` + } + + getNpub(pubkey: string): string { + if (pubkey.startsWith("npub")) { + return pubkey; + } + return nip19.npubEncode(pubkey); + } + + getUsername(pubkey: string): string { + if (pubkey.startsWith("npub")) { + pubkey = nip19.decode(pubkey).data.toString() + } + return `@${(localStorage.getItem(`${pubkey}`) || this.getNpub(pubkey))}` + } + + nip08Replace(content: string): string { + let userTags: string[] = content.match(/#\[\d+\]/gm) || [] + // is this condition right? + if (this.nip10Result.profiles.length !== userTags.length) { + return content; + } + for (let i in userTags) { + let userPubkey = this.nip10Result.profiles[i].pubkey + let npub = this.getNpub(userPubkey); + let username = this.getUsername(userPubkey); + let textWrap: TextWrap = {text: username, addLink: `href="/users/${npub}"`} + content = content.replace(userTags[i], this.wrapTextInSpan(textWrap)) + } + return content + } + + parseLightningInvoice(content: string): string { + let invoices: string[] = content.match(/(lightning:|lnbc)[a-z0-9]+/gm) || [] + for (let invoice of invoices) { + try { + content = content.replace(invoice, this.getReplacementInvoiceHtml(invoice)); + } catch (e) { + console.log("failed to decode lightning invoice"); + } + } + return content; + } + + getInvoiceAmount(invoice: string) { + if (invoice) { + const decodedInvoice = decode(invoice); + for (let s of decodedInvoice.sections) { + if (s.name === "amount") { + return Number(s.value)/1000; + } + } + } + return ""; + } + + getReplacementInvoiceHtml(invoice: string) { + const amount = this.getInvoiceAmount(invoice); + const r = `
Lightning Invoice: ${amount} sats

${invoice}

` + return r; + } + + hashtagContent(content: string): string { + let hashtagRegex = /#\w+\S/gm; + return content.replace(hashtagRegex, function(tag) { + let textWrap: TextWrap = {text: tag, cssClass: "hashtag", hashtag: `${tag.substring(1)}`} + return `${textWrap.text}`; + }); + } + + cashtagContent(content: string): string { + let cashtagRegex = /\$\w+\S/gm; + return content.replace(cashtagRegex, function(tag) { + let textWrap: TextWrap = {text: tag, cssClass: "hashtag", hashtag: `${tag.substring(1)}`} + return `${textWrap.text}`; + }); + } + + styleUsername(content: string): string { + let usernameRegex = /@\w+/gm + return content.replace(usernameRegex, function(name) { + let textWrap: TextWrap = {text: name, cssClass: "hashtag"} + return `${textWrap.text}` + }); + } + + linkify(content: string): string { + // TODO: could be improved + let urlRegex =/(\b(https?):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig; + return content.replace(urlRegex, function(url) { + // improve this? + if (url.toLowerCase().endsWith(".png") || + url.toLowerCase().endsWith(".jpg") || + url.toLowerCase().endsWith(".jpeg") || + url.toLowerCase().endsWith(".webp") || + url.toLowerCase().endsWith(".gif") || + url.toLowerCase().endsWith(".gifv") + ) { + return `

` + } + if (url.toLowerCase().endsWith("mp4") || url.toLowerCase().endsWith("mov")) { + return `

`; + } + if (isYoutubeVideo(url)) { + // kinda hacky but works + if (url.includes("youtu.be")) { + url = url.replace("youtu.be/", "youtube.com/watch?v=") + } + url = url.replace("watch?v=", "embed/") + return `

`; + } + return `

${url}

`; + }); + } + + encodeNoteAsEvent(note: string): string { + let decodedNote = nip19.decode(note).data.toString(); + let eventP: nip19.EventPointer = {id: decodedNote} + return nip19.neventEncode(eventP); + } + + npubFromNProfile(nprofile: string): string { + const decodedNProfile: nip19.ProfilePointer = nip19.decode(nprofile).data as nip19.ProfilePointer; + return nip19.npubEncode(decodedNProfile.pubkey); + } + + replaceNostrThing(content: string) { + if (!this.hasEventPointer(content)) { + return content; + } + let matches = content.match(/nostr:[a-z0-9]+/gm) || [] + for (let m in matches) { + let match = matches[m] + try { + if (match.startsWith("nostr:npub")) { + let npub = match.substring(6) + let username = this.getUsername(npub) + let textWrap: TextWrap = {text: this.ellipsis(username), npub: npub, cssClass: "user-at"} + let htmlSpan = this.wrapTextInSpan(textWrap) + content = content.replace(match, htmlSpan); + } + if (match.startsWith("nostr:nevent")) { + let nevent = match.substring(6) + let textWrap: TextWrap = {text: this.ellipsis(nevent), nevent: nevent} + content = content.replace(match, this.wrapTextInSpan(textWrap)); + } + if (match.startsWith("nostr:note")) { + let note = match.substring(6); + let textWrap: TextWrap = {text: this.ellipsis(note), nevent: this.encodeNoteAsEvent(note)} + content = content.replace(match, this.wrapTextInSpan(textWrap)); + } + if (match.startsWith("nostr:nprofile")) { + const nprofile = match.substring(6); + const npub = this.npubFromNProfile(nprofile); + let textWrap: TextWrap = {text: this.ellipsis(npub), npub: npub, cssClass: "user-at"} + content = content.replace(match, this.wrapTextInSpan(textWrap)); + } + if (match.startsWith("nostr:naddr")) { + // these are editable posts i think means long form + // so we will link to habla.news for now + // https://habla.news/a/naddr1qqxnzdesxg6rzdp4xu6nzwpnqgsf03c2gsmx5ef4c9zmxvlew04gdh7u94afnknp33qvv3c94kvwxgsrqsqqqa280a30ar + const naddr = match.substring(6); + //let textWrap: TextWrap = {text: this.ellipsis(naddr), nevent: this.encodeNAddrAsEvent(naddr)} + content = content.replace(naddr, `https://habla.news/a/${naddr}`) + content = this.linkify(content); + } + } catch (e) { + console.log(e); + } + } + return content; + } +} + + + +export class Post { + kind: number; + content: string; + pubkey: string; + npub: string; + noteId: string; + createdAt: number; + nip10Result: NIP10Result; + date: Date; + fromNow: string = ""; + username: string = ""; + picture: string = ""; + replyingTo: string[] = []; + mentions: string[] = []; + nostrNoteId: string; + nostrEventId: string; + replyCount: number; + likeCount: number; + isAReply: boolean = false; + repostingPubkey: string; + likedByMe: boolean = false; + constructor(kind: number, pubkey: string, content: string, noteId: string, createdAt: number, nip10Result: NIP10Result, repostingPubkey: string) { + this.kind = kind; + this.pubkey = pubkey; + this.npub = nip19.npubEncode(this.pubkey); + this.noteId = noteId + this.nip10Result = nip10Result; + this.createdAt = createdAt; + this.date = new Date(this.createdAt*1000); + this.setFromNow() + this.setUsername(this.pubkey); + this.setPicture(this.pubkey); + this.content = new Content(kind, content, nip10Result).getParsedContent(); + this.nostrNoteId = nip19.noteEncode(this.noteId); + this.nostrEventId = nip19.neventEncode({id: this.noteId}); + this.replyCount = 0; + this.likeCount = 0; + this.setIsAReply(); + this.repostingPubkey = repostingPubkey; + } + + getAllTags(): string[][] { + return [["e", this.noteId], ["p", this.pubkey]] + } + + setUsername(pubkey: string): void { + this.username = localStorage.getItem(`${pubkey}_name`) || this.npub; + } + + setPicture(pubkey: string): void { + this.picture = localStorage.getItem(`${pubkey}_img`) || "https://axiumradonmitigations.com/wp-content/uploads/2015/01/icon-user-default.png"; + } + + setReplyCount(count: number): void { + this.replyCount = count; + } + + setLikeCount(count: number): void { + this.likeCount = count; + } + + setFromNow(): void { + this.fromNow = dayjs(this.date).fromNow() + } + + setReplyingTo(): void { + //this.replyingTo = this.nip10Result.profiles; + } + + setPostLikedByMe(val: boolean): void { + this.likedByMe = val; + } + + setIsAReply(): void { + if (this.nip10Result.root || this.nip10Result.reply) { + this.isAReply = true; + } else { + this.isAReply = false; + } + } +} diff --git a/src/app/types/preview.ts b/src/app/types/preview.ts new file mode 100644 index 0000000..aee60b3 --- /dev/null +++ b/src/app/types/preview.ts @@ -0,0 +1,6 @@ +export interface Preview { + title: string; + description: string; + image: string; + url: string; +} diff --git a/src/app/types/user.ts b/src/app/types/user.ts new file mode 100644 index 0000000..e58bbf6 --- /dev/null +++ b/src/app/types/user.ts @@ -0,0 +1,136 @@ +import { nip19 } from 'nostr-tools'; +import { Content } from 'app/types/post'; + +// kind 0 content - nostr +export interface Kind0Content { + name?: string; + username?: string; + displayName?: string; + website?: string; + about?: string; + picture?: string; + banner?: string; + lud06?: string; + lud16?: string; + nip05?: string; + // can have other random stuff in here too +} + +export interface DBUser { + name: string; + username: string; + displayName: string; + website: string; + about: string; + picture: string; + banner: string; + lud06: string; + lud16: string; + nip05: string; + pubkey: string; + npub: string; + createdAt: string; + apiKey: string; + following?: boolean; +} + +export interface SearchUser { + pubkey: string, + picture: string +} + + +export class BaseUser { + pubkey: string; + name: string; + constructor(pubkey: string, name: string) { + this.pubkey = pubkey; + this.name = name; + } +} + + +export function dbUserToUser(dbUser: DBUser): User { + const kind0Content: Kind0Content = { + name: dbUser.name, + username: dbUser.username, + displayName: dbUser.displayName, + website: dbUser.website, + about: dbUser.about, + picture: dbUser.picture, + banner: dbUser.banner, + lud06: dbUser.lud06, + lud16: dbUser.lud16, + nip05: dbUser.nip05 + } + return new User(kind0Content, Number(dbUser.createdAt), dbUser.pubkey, dbUser.following); +} + +/* + Preprocesses a kind0 message into a User class to be nicely accessible +*/ +export class User { + name: string = ""; + username: string = ""; + displayName: string = ""; + website: string = ""; + about: string = ""; + aboutHTML: string; + picture: string = ""; + banner: string = ""; + lud06: string = ""; + lud16: string = ""; + nip05: string = ""; + pubkey: string; + npub: string; + createdAt: number; + apiKey: string; + following: boolean = false; + constructor(kind0: Kind0Content, createdAt: number, pubkey: string, following?: boolean) { + this.pubkey = pubkey; + this.npub = nip19.npubEncode(this.pubkey); + this.name = kind0.name || ""; + this.username = kind0.username || ""; + this.displayName = kind0.displayName || this.name || this.username || this.npub; + this.website = this.getClickableWebsite(kind0.website || ""); + const fake = { + reply: undefined, + root: undefined, + mentions: [], + profiles: [] + } + this.about = kind0.about || ""; + this.aboutHTML = new Content(1, kind0.about || "", fake, true).getParsedContent(); + this.picture = kind0.picture || "https://axiumradonmitigations.com/wp-content/uploads/2015/01/icon-user-default.png"; + this.banner = kind0.banner || ""; + this.lud06 = kind0.lud06 || ""; + this.lud16 = kind0.lud16 || ""; + this.nip05 = kind0.nip05 || ""; + this.createdAt = createdAt; + this.cachePubkeyDisplayName() + this.apiKey = "LIVDSRZULELA" // TODO; + if (following) { + this.setFollowing(true); + } + } + + getClickableWebsite(link: string) { + if (link === "") return link; + if (link.startsWith("http://") || link.startsWith("https://")) { + return link; + } + return `http://${link}`; + } + + cachePubkeyDisplayName() { + localStorage.setItem(`${this.pubkey}`, this.displayName); + } + + setLightningInfo(lud06: string, lud16: string) { + // decode one into the other and vice versa if only has one + } + + setFollowing(following: boolean) { + this.following = following + } +} diff --git a/src/app/types/webln.ts b/src/app/types/webln.ts new file mode 100644 index 0000000..26c57f4 --- /dev/null +++ b/src/app/types/webln.ts @@ -0,0 +1,54 @@ +interface SendPaymentResponse { + paymentHash?: string; + preimage: string; + route?: { + total_amt: number; + total_fees: number; + }; +} + +interface RequestInvoiceArgs { + amount?: string | number; + defaultAmount?: string | number; + minimumAmount?: string | number; + maximumAmount?: string | number; + defaultMemo?: string; +} + +interface RequestInvoiceResponse { + paymentRequest: string; +} + +interface GetInfoResponse { + node: { + alias: string; + pubkey: string; + color?: string; + }; +} + +interface SignMessageResponse { + message: string; + signature: string; +} + +export interface WebLN { + enabled: boolean; + getInfo(): Promise; + enable(): Promise; + makeInvoice(args: RequestInvoiceArgs): Promise; + signMessage(message: string): Promise; + verifyMessage(signature: string, message: string): Promise; + sendPayment: (pr: string) => Promise; +} + +// export interface WalletInvoice { +// pr: string; +// paymentHash: string; +// memo: string; +// amount: MilliSats; +// fees: number; +// timestamp: number; +// preimage?: string; +// state: WalletInvoiceState; +// } \ No newline at end of file diff --git a/src/app/types/zap.ts b/src/app/types/zap.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/app/utils.ts b/src/app/utils.ts new file mode 100644 index 0000000..faebed9 --- /dev/null +++ b/src/app/utils.ts @@ -0,0 +1,150 @@ + +import dayjs from 'dayjs'; +import relativeTime from 'dayjs/plugin/relativeTime'; +import { Post } from './types/post'; +import { User } from './types/user'; +dayjs.extend(relativeTime); + + +export function range (start: number, end: number) { + return [...Array(1+end-start).keys()].map(v => start+v) +} + +export function ellipsis(value: string): string { + // truncates the middle of the string + if (value.length < 40) return value; + let third: number = value.length / 8; + let finalThird: number = value.length - third; + return value.substring(0, third) + ":" + value.substring(finalThird) +} + + +export function humantime(value: number): string { + let date = new Date(value*1000) + return dayjs(date).fromNow() +} + +export class Paginator { + previousUntil: number | null; + previousSince: number | null; + until: number = 0; + since: number = 0; + baseTimeDiff: number; + originalBaseTimeDiff: number; + constructor(until: number = 0, since: number = 0, baseTimeDiff: number = 15) { + this.until = until; + this.setDefaultUntil(); + this.baseTimeDiff = baseTimeDiff; + this.originalBaseTimeDiff = this.baseTimeDiff; + if (since === 0) { + this.setDefaultSince() + } else { + this.setDefaultSince(since); + } + this.previousSince = since; + this.previousUntil = until; + } + + incrementFilterTimes(posts: Post[]): void { + const oldestPost = posts.at(-1); + if (oldestPost) { + this.revertBackToOriginalBaseTimeDiff(); + this.setUntil(oldestPost.createdAt); + this.setSince(oldestPost.createdAt); + } else { + // posts must be empty so increment more + this.updateBaseTimeToFindPosts(); + this.setDefaultUntil(); + // expand time until we find something + this.setDefaultSince(); + } + } + + incrementUserTimes(users: User[]): void { + const oldestPost = users.at(-1); + if (oldestPost) { + this.revertBackToOriginalBaseTimeDiff(); + this.setUntil(oldestPost.createdAt); + this.setSince(oldestPost.createdAt); + } else { + // posts must be empty so increment more + this.updateBaseTimeToFindPosts(); + this.setDefaultUntil(); + // expand time until we find something + this.setDefaultSince(); + } + } + + resetFilterTimes(newBaseTimeDiff: number = 0): void { + this.setDefaultSince(); + this.setDefaultUntil(); + if (newBaseTimeDiff !== 0) { + this.baseTimeDiff = newBaseTimeDiff; + } + } + + updateBaseTimeToFindPosts() { + this.baseTimeDiff = this.baseTimeDiff * 10; + } + + revertBackToOriginalBaseTimeDiff() { + // revert back once we have found posts + this.baseTimeDiff = this.originalBaseTimeDiff; + } + + getSinceAsDate(): Date { + return new Date(this.since*1000); + } + + getUntilAsDate(): Date { + return new Date(this.until*1000); + } + + getSinceFromNow(): string { + const sinceDate = this.getSinceAsDate(); + return dayjs(sinceDate).fromNow(); + } + + getUntilFromNow(): string { + const untilDate = this.getSinceAsDate(); + return dayjs(untilDate).fromNow(); + } + + printTimes(): void { + const diff = this.getUntilAsDate().getTime() - this.getSinceAsDate().getTime(); + console.log(`Until: ${this.getUntilFromNow()} | Since: ${this.getSinceFromNow()}`); + console.log(`Diff: ${diff}`); + } + + printVars(): void { + console.log(`Until: ${this.until}`); + console.log(`Since: ${this.since}`); + console.log(`baseTimeDiff: ${this.baseTimeDiff}`); + } + + private setDefaultSince(addedMinutes: number = 0): void { + // Math.floor(Date.now() / 1000) + let now = new Date(); + const sinceDate = Math.floor(now.setMinutes(now.getMinutes() - this.baseTimeDiff - addedMinutes) / 1000); + this.since = sinceDate; + } + + private setDefaultUntil(): void { + this.until = Math.floor(Date.now() / 1000); + } + + private getNewSince(createdAt: number, addedMinutes: number = 0): number { + const now = new Date(createdAt*1000); + return Math.floor(now.setMinutes(now.getMinutes() - this.baseTimeDiff - addedMinutes) / 1000); + } + + private setUntil(createdAt: number): void { + this.previousUntil = this.until - (2 * 1000) // minus two minutes; + this.until = createdAt; + } + + private setSince(createdAt: number, addedMinutes: number = 0): void { + this.previousSince = this.since; + this.since = this.getNewSince(createdAt, addedMinutes); + } +} diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..233ccf9 --- /dev/null +++ b/src/index.html @@ -0,0 +1,58 @@ + + + + Angor Hub + + + + + + + + + + + + + + + + + + + + + + + + + + + + Angor logo +
+
+
+
+
+
+ + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..147a38e --- /dev/null +++ b/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { AppComponent } from 'app/app.component'; +import { appConfig } from 'app/app.config'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); diff --git a/src/styles/styles.scss b/src/styles/styles.scss new file mode 100644 index 0000000..1e13e93 --- /dev/null +++ b/src/styles/styles.scss @@ -0,0 +1,7 @@ +body { + overflow-x: hidden; +} + +.overscroll-y-contain { + overflow: hidden !important; +} diff --git a/src/styles/tailwind.scss b/src/styles/tailwind.scss new file mode 100644 index 0000000..08c702c --- /dev/null +++ b/src/styles/tailwind.scss @@ -0,0 +1,4 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Main Tailwind file for injecting utilities. +/* ----------------------------------------------------------------------------------------------------- */ +@tailwind utilities; diff --git a/src/styles/vendors.scss b/src/styles/vendors.scss new file mode 100644 index 0000000..9cad066 --- /dev/null +++ b/src/styles/vendors.scss @@ -0,0 +1,9 @@ +/* ----------------------------------------------------------------------------------------------------- */ +/* @ Import third party library styles here. +/* ----------------------------------------------------------------------------------------------------- */ + +/* Perfect scrollbar */ +@import 'perfect-scrollbar/css/perfect-scrollbar.css'; + +/* Quill */ +@import 'quill/dist/quill.snow.css'; diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..97dc9f4 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,314 @@ +const path = require('path'); +const colors = require('tailwindcss/colors'); +const defaultTheme = require('tailwindcss/defaultTheme'); +const generatePalette = require( + path.resolve(__dirname, 'src/@angor/tailwind/utils/generate-palette') +); + +/** + * Custom palettes + * + * Uses the generatePalette helper method to generate + * Tailwind-like color palettes automatically + */ +const customPalettes = { + brand: generatePalette('#086c81'), +}; + +/** + * Themes + */ +const themes = { + // Default theme is required for theming system to work correctly! + default: { + primary: { + ...colors.indigo, + DEFAULT: colors.indigo[600], + }, + accent: { + ...colors.slate, + DEFAULT: colors.slate[800], + }, + warn: { + ...colors.red, + DEFAULT: colors.red[600], + }, + 'on-warn': { + 500: colors.red['50'], + }, + }, + // Rest of the themes will use the 'default' as the base + // theme and will extend it with their given configuration. + brand: { + primary: customPalettes.brand, + }, + teal: { + primary: { + ...colors.teal, + DEFAULT: colors.teal[600], + }, + }, + rose: { + primary: colors.rose, + }, + purple: { + primary: { + ...colors.purple, + DEFAULT: colors.purple[600], + }, + }, + amber: { + primary: colors.amber, + }, +}; + +/** + * Tailwind configuration + */ +const config = { + darkMode: 'class', + content: ['./src/**/*.{html,scss,ts}'], + important: true, + theme: { + fontSize: { + xs: '0.625rem', + sm: '0.75rem', + md: '0.8125rem', + base: '0.875rem', + lg: '1rem', + xl: '1.125rem', + '2xl': '1.25rem', + '3xl': '1.5rem', + '4xl': '2rem', + '5xl': '2.25rem', + '6xl': '2.5rem', + '7xl': '3rem', + '8xl': '4rem', + '9xl': '6rem', + '10xl': '8rem', + }, + screens: { + sm: '600px', + md: '960px', + lg: '1280px', + xl: '1440px', + }, + extend: { + animation: { + 'spin-slow': 'spin 3s linear infinite', + }, + colors: { + gray:{ + '50': '#e5eef0', + '100': '#cbdde1', + '200': '#9bbac3', + '300': '#6b98a4', + '400': '#3b7586', + '500': '#083b46', + '600': '#07343e', + '700': '#052b33', + '800': '#032128', + '900': '#022229', + '950': '#011517' + } + , + }, + flex: { + 0: '0 0 auto', + }, + fontFamily: { + sans: `"Inter var", ${defaultTheme.fontFamily.sans.join(',')}`, + mono: `"IBM Plex Mono", ${defaultTheme.fontFamily.mono.join(',')}`, + }, + opacity: { + 12: '0.12', + 38: '0.38', + 87: '0.87', + }, + rotate: { + '-270': '270deg', + 15: '15deg', + 30: '30deg', + 60: '60deg', + 270: '270deg', + }, + scale: { + '-1': '-1', + }, + zIndex: { + '-1': -1, + 49: 49, + 60: 60, + 70: 70, + 80: 80, + 90: 90, + 99: 99, + 999: 999, + 9999: 9999, + 99999: 99999, + }, + spacing: { + 13: '3.25rem', + 15: '3.75rem', + 18: '4.5rem', + 22: '5.5rem', + 26: '6.5rem', + 30: '7.5rem', + 50: '12.5rem', + 90: '22.5rem', + + // Bigger values + 100: '25rem', + 120: '30rem', + 128: '32rem', + 140: '35rem', + 160: '40rem', + 180: '45rem', + 192: '48rem', + 200: '50rem', + 240: '60rem', + 256: '64rem', + 280: '70rem', + 320: '80rem', + 360: '90rem', + 400: '100rem', + 480: '120rem', + + // Fractional values + '1/2': '50%', + '1/3': '33.333333%', + '2/3': '66.666667%', + '1/4': '25%', + '2/4': '50%', + '3/4': '75%', + }, + minHeight: ({ theme }) => ({ + ...theme('spacing'), + }), + maxHeight: { + none: 'none', + }, + minWidth: ({ theme }) => ({ + ...theme('spacing'), + screen: '100vw', + }), + maxWidth: ({ theme }) => ({ + ...theme('spacing'), + screen: '100vw', + }), + transitionDuration: { + 400: '400ms', + }, + transitionTimingFunction: { + drawer: 'cubic-bezier(0.25, 0.8, 0.25, 1)', + }, + + // @tailwindcss/typography + typography: ({ theme }) => ({ + DEFAULT: { + css: { + color: 'var(--angor-text-default)', + '[class~="lead"]': { + color: 'var(--angor-text-secondary)', + }, + a: { + color: 'var(--angor-primary-500)', + }, + strong: { + color: 'var(--angor-text-default)', + }, + 'ol > li::before': { + color: 'var(--angor-text-secondary)', + }, + 'ul > li::before': { + backgroundColor: 'var(--angor-text-hint)', + }, + hr: { + borderColor: 'var(--angor-border)', + }, + blockquote: { + color: 'var(--angor-text-default)', + borderLeftColor: 'var(--angor-border)', + }, + h1: { + color: 'var(--angor-text-default)', + }, + h2: { + color: 'var(--angor-text-default)', + }, + h3: { + color: 'var(--angor-text-default)', + }, + h4: { + color: 'var(--angor-text-default)', + }, + 'figure figcaption': { + color: 'var(--angor-text-secondary)', + }, + code: { + color: 'var(--angor-text-default)', + fontWeight: '500', + }, + 'a code': { + color: 'var(--angor-primary)', + }, + pre: { + color: theme('colors.white'), + backgroundColor: theme('colors.gray.800'), + }, + thead: { + color: 'var(--angor-text-default)', + borderBottomColor: 'var(--angor-border)', + }, + 'tbody tr': { + borderBottomColor: 'var(--angor-border)', + }, + 'ol[type="A" s]': false, + 'ol[type="a" s]': false, + 'ol[type="I" s]': false, + 'ol[type="i" s]': false, + }, + }, + sm: { + css: { + code: { + fontSize: '1em', + }, + pre: { + fontSize: '1em', + }, + table: { + fontSize: '1em', + }, + }, + }, + }), + }, + }, + corePlugins: { + appearance: false, + container: false, + float: false, + clear: false, + placeholderColor: false, + placeholderOpacity: false, + verticalAlign: false, + }, + plugins: [ + // Angor - Tailwind plugins + require( + path.resolve(__dirname, 'src/@angor/tailwind/plugins/utilities') + ), + require( + path.resolve(__dirname, 'src/@angor/tailwind/plugins/icon-size') + ), + require(path.resolve(__dirname, 'src/@angor/tailwind/plugins/theming'))({ + themes, + }), + + // Other third party and/or custom plugins + require('@tailwindcss/typography')({ modifiers: ['sm', 'lg'] }), + ], +}; + +module.exports = config; diff --git a/transloco.config.js b/transloco.config.js new file mode 100644 index 0000000..c2f4951 --- /dev/null +++ b/transloco.config.js @@ -0,0 +1,3 @@ +module.exports = { + rootTranslationsPath: 'src/assets/i18n/', +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..7076a42 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,10 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/app", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5f23277 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "compileOnSave": false, + "compilerOptions": { + "baseUrl": "./src", + "outDir": "./dist/out-tsc", + "esModuleInterop": true, + "sourceMap": true, + "declaration": false, + "experimentalDecorators": true, + "moduleResolution": "bundler", + "importHelpers": true, + "target": "ES2022", + "module": "ES2022", + "useDefineForClassFields": false, + "lib": ["ES2022", "dom"] + }, + "angularCompilerOptions": { + "enableI18nLegacyMessageIdFormat": false + } +} diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 0000000..272b327 --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,9 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "types": ["jasmine"] + }, + "include": ["src/**/*.spec.ts", "src/**/*.d.ts"] +}