diff --git a/.eslintrc.json b/.eslintrc.json index b99e945..6c8e9d3 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,13 +3,38 @@ "ecmaVersion": 9, "sourceType": "module" }, - "plugins": ["prettier"], - "extends": ["eslint:recommended", "plugin:prettier/recommended"], + "plugins": ["@typescript-eslint", "prettier"], + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], "env": { "browser": true, "es6": true }, "rules": { "no-console": ["warn", {"allow": ["warn", "error"]}] - } + }, + "overrides": [ + { + "files": ["src/**/*"], + "parserOptions": { + "parser": "@typescript-eslint/parser", + "project": "./tsconfig.json" + }, + "extends": [ + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/recommended-requiring-type-checking" + ], + "env": { + "browser": true + }, + "rules": { + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-member-accessibility": "error", + "@typescript-eslint/prefer-return-this-type": "error", + "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}] + } + } + ] } diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 80a6b81..09ae8b4 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -14,7 +14,7 @@ jobs: # - uses: actions/checkout@v3 # - uses: actions/setup-node@v3 # with: - # node-version: 16 + # node-version: 20 # - run: npm ci # - run: npm test @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 20 registry-url: https://registry.npmjs.org/ - run: npm ci - run: npm publish diff --git a/.gitignore b/.gitignore index 44d646d..fd10abd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +.DS_Store node_modules dist/ diff --git a/LICENSE.txt b/LICENSE.txt index 4a386e0..4c83cd8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ Copyright (c) 2020, Josh Pullen Copyright (c) 2020, adroitwhiz -Copyright (c) 2020, Florrie Haero Miller +Copyright (c) 2020, Quasar Nebula Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/package-lock.json b/package-lock.json index ad939f1..4ccc6f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,34 @@ { "name": "leopard", - "version": "1.5.1", - "lockfileVersion": 2, + "version": "1.5.2", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "leopard", - "version": "1.5.1", + "version": "1.5.2", "license": "MIT", "devDependencies": { - "eslint": "^8.20.0", - "eslint-config-prettier": "^8.5.0", + "@rollup/plugin-typescript": "^11.1.5", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "^6.15.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "prettier": "^2.7.1", "rollup": "^2.77.0", "rollup-plugin-postcss": "^4.0.2", - "rollup-plugin-terser": "^7.0.2" + "rollup-plugin-terser": "^7.0.2", + "typescript": "^4.9.5" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@babel/code-frame": { @@ -53,16 +66,40 @@ "node": ">=6.9.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -71,44 +108,51 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/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==", + "node_modules/@eslint/js": { + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@jridgewell/gen-mapping": { @@ -169,6 +213,95 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "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/@rollup/plugin-typescript": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.5.tgz", + "integrity": "sha512-rnMHrGBB0IUEv69Q8/JGRD/n4/n6b3nfpufUu26axhUcboUzv/twfZU8fIBbTOphRAe0v8EyxzeDpKXqGHfyDA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.14.0||^3.0.0||^4.0.0", + "tslib": "*", + "typescript": ">=3.7.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + }, + "tslib": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -184,16 +317,229 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "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/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/node": { "version": "18.0.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.5.tgz", "integrity": "sha512-En7tneq+j0qAiVwysBD79y86MT3ModuoIJbe7JXp+sb5UAjInSShmK3nXXMioBzfF7rXC12hv12d4IyCVwN4dA==", "dev": true }, + "node_modules/@types/semver": { + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.6.tgz", + "integrity": "sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.15.0.tgz", + "integrity": "sha512-j5qoikQqPccq9QoBAupOP+CBu8BaJ8BLjaXSioDISeTZkVO3ig7oSIKh3H+rEpee7xCXtWwSB4KIL5l6hWZzpg==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/type-utils": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.15.0.tgz", + "integrity": "sha512-MkgKNnsjC6QwcMdlNAel24jjkEO/0hQaMDLqP4S9zq5HBAUJNQB6y+3DwLjX7b3l2b37eNAxMPLwb3/kh8VKdA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.15.0.tgz", + "integrity": "sha512-+BdvxYBltqrmgCNu4Li+fGDIkW9n//NrruzG9X1vBzaNK+ExVXPoGB71kneaVw/Jp+4rH/vaMAGC6JfMbHstVg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.15.0.tgz", + "integrity": "sha512-CnmHKTfX6450Bo49hPg2OkIm/D/TVYV7jO1MCfPYGwf6x3GO0VU8YMO5AYMn+u3X05lRRxA4fWCz87GFQV6yVQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.15.0", + "@typescript-eslint/utils": "6.15.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.15.0.tgz", + "integrity": "sha512-yXjbt//E4T/ee8Ia1b5mGlbNj9fB9lJP4jqLbZualwpP2BCQ5is6BcWwxpIsY4XKAhmdv3hrW92GdtJbatC6dQ==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.15.0.tgz", + "integrity": "sha512-7mVZJN7Hd15OmGuWrp2T9UvqR2Ecg+1j/Bp1jXUEY2GZKV6FXlOIoqVDmLpBiEiq3katvj/2n2mR0SDwtloCew==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/visitor-keys": "6.15.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.15.0.tgz", + "integrity": "sha512-eF82p0Wrrlt8fQSRL0bGXzK5nWPRV2dYQZdajcfzOD9+cQz9O7ugifrJxclB+xVOvWvagXfqS4Es7vpLP4augw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.15.0", + "@typescript-eslint/types": "6.15.0", + "@typescript-eslint/typescript-estree": "6.15.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.15.0.tgz", + "integrity": "sha512-1zvtdC1a9h5Tb5jU9x3ADNXO9yjP8rXlaoChu0DQX40vf5ACVpYIVIZhIMZ6d5sDXH7vq4dsZBT1fEGj8D2n2w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.15.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -248,6 +594,21 @@ "node": ">=4" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -270,6 +631,18 @@ "concat-map": "0.0.1" } }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/browserslist": { "version": "4.21.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", @@ -590,6 +963,18 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "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/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -691,46 +1076,49 @@ } }, "node_modules/eslint": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.20.0.tgz", - "integrity": "sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", + "@humanwhocodes/config-array": "^0.11.13", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" @@ -743,9 +1131,9 @@ } }, "node_modules/eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "version": "8.8.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz", + "integrity": "sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==", "dev": true, "bin": { "eslint-config-prettier": "bin/cli.js" @@ -776,9 +1164,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -786,42 +1174,21 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/ansi-styles": { @@ -839,12 +1206,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/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/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -900,18 +1261,6 @@ "node": ">=8" } }, - "node_modules/eslint/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/eslint/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -925,23 +1274,26 @@ } }, "node_modules/espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.7.1", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -1004,6 +1356,34 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", + "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -1016,6 +1396,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -1028,6 +1417,34 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/flat-cache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", @@ -1073,12 +1490,6 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, "node_modules/generic-names": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", @@ -1121,9 +1532,9 @@ } }, "node_modules/globals": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", - "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1135,6 +1546,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1175,9 +1612,9 @@ } }, "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -1196,9 +1633,9 @@ } }, "node_modules/import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "dependencies": { "parent-module": "^1.0.0", @@ -1206,6 +1643,9 @@ }, "engines": { "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/import-from": { @@ -1287,6 +1727,24 @@ "node": ">=0.10.0" } }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1334,6 +1792,18 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -1377,6 +1847,21 @@ "node": ">= 12.13.0" } }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", @@ -1401,6 +1886,18 @@ "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "dev": true }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -1413,6 +1910,28 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1432,10 +1951,16 @@ "dev": true }, "node_modules/nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "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" + } + ], "peer": true, "bin": { "nanoid": "bin/nanoid.cjs" @@ -1490,17 +2015,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -1515,6 +2040,36 @@ "node": ">=4" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-queue": { "version": "6.6.2", "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", @@ -1555,6 +2110,15 @@ "node": ">=6" } }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1579,12 +2143,33 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", "dev": true }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", @@ -1598,9 +2183,9 @@ } }, "node_modules/postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", + "version": "8.4.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", + "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", "dev": true, "funding": [ { @@ -1610,11 +2195,15 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "peer": true, "dependencies": { - "nanoid": "^3.3.4", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" }, @@ -2199,14 +2788,34 @@ } }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "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/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/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -2216,18 +2825,6 @@ "safe-buffer": "^5.1.0" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -2254,6 +2851,16 @@ "node": ">=4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2406,6 +3013,29 @@ "estree-walker": "^0.6.1" } }, + "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/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -2432,6 +3062,21 @@ "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", "dev": true }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/serialize-javascript": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", @@ -2462,6 +3107,15 @@ "node": ">=8" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -2625,6 +3279,38 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true, + "optional": true, + "peer": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2649,7 +3335,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/update-browserslist-db": { + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", @@ -2690,12 +3389,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, - "node_modules/v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2711,21 +3404,18 @@ "node": ">= 8" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -2734,1954 +3424,18 @@ "engines": { "node": ">= 6" } - } - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } }, - "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "engines": { + "node": ">=10" }, - "dependencies": { - "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 - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@humanwhocodes/config-array": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.5.tgz", - "integrity": "sha512-ObyMyWxZiCu/yTisA7uzx81s40xR2fD5Cg/2Kq7G02ajkNubJf6BopgDTmDyc3U7sXpNKM8cYOw7s7Tyr+DnCw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz", - "integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true - }, - "@types/color-name": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", - "dev": true - }, - "@types/node": { - "version": "18.0.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.0.5.tgz", - "integrity": "sha512-En7tneq+j0qAiVwysBD79y86MT3ModuoIJbe7JXp+sb5UAjInSShmK3nXXMioBzfF7rXC12hv12d4IyCVwN4dA==", - "dev": true - }, - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "browserslist": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.2.tgz", - "integrity": "sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001366", - "electron-to-chromium": "^1.4.188", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.4" - } - }, - "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 - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "dev": true, - "requires": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, - "caniuse-lite": { - "version": "1.0.30001367", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001367.tgz", - "integrity": "sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "colord": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", - "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", - "dev": true - }, - "commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", - "dev": true, - "requires": { - "source-map": "^0.6.1" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } - }, - "css-declaration-sorter": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.0.tgz", - "integrity": "sha512-OGT677UGHJTAVMRhPO+HJ4oKln3wkBTwtDFH0ojbqm+MJm6xuDMHp2nkhh/ThaBqq20IbraBQSWKfSLNHQO9Og==", - "dev": true, - "requires": {} - }, - "css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", - "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "dev": true, - "requires": { - "mdn-data": "2.0.14", - "source-map": "^0.6.1" - } - }, - "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 - }, - "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 - }, - "cssnano": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.12.tgz", - "integrity": "sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==", - "dev": true, - "requires": { - "cssnano-preset-default": "^5.2.12", - "lilconfig": "^2.0.3", - "yaml": "^1.10.2" - } - }, - "cssnano-preset-default": { - "version": "5.2.12", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.2.12.tgz", - "integrity": "sha512-OyCBTZi+PXgylz9HAA5kHyoYhfGcYdwFmyaJzWnzxuGRtnMw/kR6ilW9XzlzlRAtB6PLT/r+prYgkef7hngFew==", - "dev": true, - "requires": { - "css-declaration-sorter": "^6.3.0", - "cssnano-utils": "^3.1.0", - "postcss-calc": "^8.2.3", - "postcss-colormin": "^5.3.0", - "postcss-convert-values": "^5.1.2", - "postcss-discard-comments": "^5.1.2", - "postcss-discard-duplicates": "^5.1.0", - "postcss-discard-empty": "^5.1.1", - "postcss-discard-overridden": "^5.1.0", - "postcss-merge-longhand": "^5.1.6", - "postcss-merge-rules": "^5.1.2", - "postcss-minify-font-values": "^5.1.0", - "postcss-minify-gradients": "^5.1.1", - "postcss-minify-params": "^5.1.3", - "postcss-minify-selectors": "^5.2.1", - "postcss-normalize-charset": "^5.1.0", - "postcss-normalize-display-values": "^5.1.0", - "postcss-normalize-positions": "^5.1.1", - "postcss-normalize-repeat-style": "^5.1.1", - "postcss-normalize-string": "^5.1.0", - "postcss-normalize-timing-functions": "^5.1.0", - "postcss-normalize-unicode": "^5.1.0", - "postcss-normalize-url": "^5.1.0", - "postcss-normalize-whitespace": "^5.1.1", - "postcss-ordered-values": "^5.1.3", - "postcss-reduce-initial": "^5.1.0", - "postcss-reduce-transforms": "^5.1.0", - "postcss-svgo": "^5.1.0", - "postcss-unique-selectors": "^5.1.1" - } - }, - "cssnano-utils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-3.1.0.tgz", - "integrity": "sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==", - "dev": true, - "requires": {} - }, - "csso": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", - "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", - "dev": true, - "requires": { - "css-tree": "^1.1.2" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true - }, - "domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "electron-to-chromium": { - "version": "1.4.192", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.192.tgz", - "integrity": "sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==", - "dev": true - }, - "entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "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==", - "dev": true - }, - "eslint": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.20.0.tgz", - "integrity": "sha512-d4ixhz5SKCa1D6SCPrivP7yYVi7nyD6A4vs6HIAul9ujBzcEmZVM3/0NN/yu5nKhmO1wjp5xQ46iRfmDGlOviA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.9.2", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.2", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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 - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "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 - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", - "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", - "dev": true, - "requires": {} - }, - "eslint-plugin-prettier": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz", - "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.2.tgz", - "integrity": "sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==", - "dev": true, - "requires": { - "acorn": "^8.7.1", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "generic-names": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/generic-names/-/generic-names-4.0.0.tgz", - "integrity": "sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==", - "dev": true, - "requires": { - "loader-utils": "^3.2.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "globals": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.16.0.tgz", - "integrity": "sha512-A1lrQfpNF+McdPOnnFqY3kSN0AFTy485bTi1bkLk4mVPODIUEcSfhHgRqA+QdXPksrSTTztYXx37NFV+GpGk3Q==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "icss-replace-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", - "dev": true - }, - "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, - "requires": {} - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "import-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-3.0.0.tgz", - "integrity": "sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==", - "dev": true, - "requires": { - "import-from": "^3.0.0" - } - }, - "import-fresh": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", - "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/import-from/-/import-from-3.0.0.tgz", - "integrity": "sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "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 - } - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-core-module": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz", - "integrity": "sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lilconfig": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", - "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", - "dev": true - }, - "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 - }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "dev": true - }, - "mdn-data": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "dev": true - }, - "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 - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "nanoid": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", - "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", - "dev": true, - "peer": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "dev": true - }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true - }, - "nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true - }, - "p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "dev": true, - "requires": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - } - }, - "p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "dev": true, - "requires": { - "p-finally": "^1.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "dev": true - }, - "postcss": { - "version": "8.4.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.14.tgz", - "integrity": "sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig==", - "dev": true, - "peer": true, - "requires": { - "nanoid": "^3.3.4", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - } - }, - "postcss-calc": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.2.4.tgz", - "integrity": "sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.9", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-colormin": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.3.0.tgz", - "integrity": "sha512-WdDO4gOFG2Z8n4P8TWBpshnL3JpmNmJwdnfP2gbk2qBA8PWwOYcmjmI/t3CmMeL72a7Hkd+x/Mg9O2/0rD54Pg==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "colord": "^2.9.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-convert-values": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.1.2.tgz", - "integrity": "sha512-c6Hzc4GAv95B7suy4udszX9Zy4ETyMCgFPUDtWjdFTKH1SE9eFY/jEpHSwTH1QPuwxHpWslhckUQWbNRM4ho5g==", - "dev": true, - "requires": { - "browserslist": "^4.20.3", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-discard-comments": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.1.2.tgz", - "integrity": "sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==", - "dev": true, - "requires": {} - }, - "postcss-discard-duplicates": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.1.0.tgz", - "integrity": "sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==", - "dev": true, - "requires": {} - }, - "postcss-discard-empty": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.1.1.tgz", - "integrity": "sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==", - "dev": true, - "requires": {} - }, - "postcss-discard-overridden": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.1.0.tgz", - "integrity": "sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==", - "dev": true, - "requires": {} - }, - "postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "requires": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - } - }, - "postcss-merge-longhand": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.1.6.tgz", - "integrity": "sha512-6C/UGF/3T5OE2CEbOuX7iNO63dnvqhGZeUnKkDeifebY0XqkkvrctYSZurpNE902LDf2yKwwPFgotnfSoPhQiw==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^5.1.0" - } - }, - "postcss-merge-rules": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.1.2.tgz", - "integrity": "sha512-zKMUlnw+zYCWoPN6yhPjtcEdlJaMUZ0WyVcxTAmw3lkkN/NDMRkOkiuctQEoWAOvH7twaxUUdvBWl0d4+hifRQ==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^3.1.0", - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-minify-font-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.1.0.tgz", - "integrity": "sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-gradients": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.1.1.tgz", - "integrity": "sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==", - "dev": true, - "requires": { - "colord": "^2.9.1", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-params": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.1.3.tgz", - "integrity": "sha512-bkzpWcjykkqIujNL+EVEPOlLYi/eZ050oImVtHU7b4lFS82jPnsCb44gvC6pxaNt38Els3jWYDHTjHKf0koTgg==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-minify-selectors": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.2.1.tgz", - "integrity": "sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "postcss-modules": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/postcss-modules/-/postcss-modules-4.3.1.tgz", - "integrity": "sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==", - "dev": true, - "requires": { - "generic-names": "^4.0.0", - "icss-replace-symbols": "^1.1.0", - "lodash.camelcase": "^4.3.0", - "postcss-modules-extract-imports": "^3.0.0", - "postcss-modules-local-by-default": "^4.0.0", - "postcss-modules-scope": "^3.0.0", - "postcss-modules-values": "^4.0.0", - "string-hash": "^1.1.1" - } - }, - "postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true, - "requires": {} - }, - "postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", - "dev": true, - "requires": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" - } - }, - "postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.4" - } - }, - "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, - "requires": { - "icss-utils": "^5.0.0" - } - }, - "postcss-normalize-charset": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.1.0.tgz", - "integrity": "sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==", - "dev": true, - "requires": {} - }, - "postcss-normalize-display-values": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.1.0.tgz", - "integrity": "sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-positions": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.1.1.tgz", - "integrity": "sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-repeat-style": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.1.1.tgz", - "integrity": "sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-string": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.1.0.tgz", - "integrity": "sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-timing-functions": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.1.0.tgz", - "integrity": "sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-unicode": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.1.0.tgz", - "integrity": "sha512-J6M3MizAAZ2dOdSjy2caayJLQT8E8K9XjLce8AUQMwOrCvjCHv24aLC/Lps1R1ylOfol5VIDMaM/Lo9NGlk1SQ==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-url": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.1.0.tgz", - "integrity": "sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==", - "dev": true, - "requires": { - "normalize-url": "^6.0.1", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-normalize-whitespace": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.1.1.tgz", - "integrity": "sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-ordered-values": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.1.3.tgz", - "integrity": "sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==", - "dev": true, - "requires": { - "cssnano-utils": "^3.1.0", - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-reduce-initial": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.1.0.tgz", - "integrity": "sha512-5OgTUviz0aeH6MtBjHfbr57tml13PuedK/Ecg8szzd4XRMbYxH4572JFG067z+FqBIf6Zp/d+0581glkvvWMFw==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "caniuse-api": "^3.0.0" - } - }, - "postcss-reduce-transforms": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.1.0.tgz", - "integrity": "sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0" - } - }, - "postcss-selector-parser": { - "version": "6.0.10", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", - "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", - "dev": true, - "requires": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - } - }, - "postcss-svgo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.1.0.tgz", - "integrity": "sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==", - "dev": true, - "requires": { - "postcss-value-parser": "^4.2.0", - "svgo": "^2.7.0" - } - }, - "postcss-unique-selectors": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.1.1.tgz", - "integrity": "sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==", - "dev": true, - "requires": { - "postcss-selector-parser": "^6.0.5" - } - }, - "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 - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", - "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "promise.series": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/promise.series/-/promise.series-0.2.0.tgz", - "integrity": "sha1-LMfr6Vn8OmYZwEq029yeRS2GS70=", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.77.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.77.0.tgz", - "integrity": "sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==", - "dev": true, - "requires": { - "fsevents": "~2.3.2" - } - }, - "rollup-plugin-postcss": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz", - "integrity": "sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "concat-with-sourcemaps": "^1.1.0", - "cssnano": "^5.0.1", - "import-cwd": "^3.0.0", - "p-queue": "^6.6.2", - "pify": "^5.0.0", - "postcss-load-config": "^3.0.0", - "postcss-modules": "^4.0.0", - "promise.series": "^0.2.0", - "resolve": "^1.19.0", - "rollup-pluginutils": "^2.8.2", - "safe-identifier": "^0.4.2", - "style-inject": "^0.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", - "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", - "dev": true, - "requires": { - "@types/color-name": "^1.1.1", - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "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 - }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "rollup-plugin-terser": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz", - "integrity": "sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "jest-worker": "^26.2.1", - "serialize-javascript": "^4.0.0", - "terser": "^5.0.0" - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - } - }, - "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==", - "dev": true - }, - "safe-identifier": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/safe-identifier/-/safe-identifier-0.4.2.tgz", - "integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==", - "dev": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "dev": true, - "peer": true - }, - "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, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "stable": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", - "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", - "dev": true - }, - "string-hash": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/string-hash/-/string-hash-1.1.3.tgz", - "integrity": "sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "style-inject": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz", - "integrity": "sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==", - "dev": true - }, - "stylehacks": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", - "integrity": "sha512-SzLmvHQTrIWfSgljkQCw2++C9+Ne91d/6Sp92I8c5uHTcy/PgeHamwITIbBW9wnFTY/3ZfSXR9HIL6Ikqmcu6Q==", - "dev": true, - "requires": { - "browserslist": "^4.16.6", - "postcss-selector-parser": "^6.0.4" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true - }, - "svgo": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", - "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", - "dev": true, - "requires": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^4.1.3", - "css-tree": "^1.1.3", - "csso": "^4.2.0", - "picocolors": "^1.0.0", - "stable": "^0.1.8" - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "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 - } - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.4.tgz", - "integrity": "sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true - }, - "v8-compile-cache": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", - "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true } } } diff --git a/package.json b/package.json index 9c8acd1..48a1009 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,45 @@ { "name": "leopard", - "version": "1.5.1", - "description": "Make Scratch-like projects with JavaScript.", + "version": "1.5.2", "private": false, - "browser": true, - "main": "dist/index.umd.js", - "module": "dist/index.esm.js", - "scripts": { - "dev": "rollup -c --watch", - "build": "rollup -c", - "prepare": "npm run build", - "lint": "eslint \"./src/**.js\"" + "description": "Make Scratch-like projects with JavaScript.", + "homepage": "https://leopardjs.com/", + "bugs": { + "url": "https://github.com/leopard-js/leopard/issues" }, - "files": [ - "dist/" - ], "repository": { "type": "git", - "url": "git+https://github.com/PullJosh/leopard.git" + "url": "https://github.com/leopard-js/leopard.git" }, + "license": "MIT", "author": "Josh Pullen (https://joshuapullen.com/)", "contributors": [ "adroitwhiz (https://github.com/adroitwhiz)", - "Florrie Haero Miller (https://github.com/towerofnix)" + "Quasar Nebula (https://github.com/towerofnix)" ], - "license": "MIT", - "bugs": { - "url": "https://github.com/PullJosh/leopard/issues" + "main": "dist/index.umd.js", + "module": "dist/index.esm.js", + "browser": true, + "files": [ + "dist/" + ], + "scripts": { + "build": "rollup -c", + "dev": "rollup -c --watch", + "lint": "eslint \"./src/**.ts\"", + "prepare": "npm run build" }, - "homepage": "https://leopardjs.com/", "devDependencies": { - "eslint": "^8.20.0", - "eslint-config-prettier": "^8.5.0", + "@rollup/plugin-typescript": "^11.1.5", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "^6.15.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "prettier": "^2.7.1", "rollup": "^2.77.0", "rollup-plugin-postcss": "^4.0.2", - "rollup-plugin-terser": "^7.0.2" + "rollup-plugin-terser": "^7.0.2", + "typescript": "^4.9.5" } } diff --git a/rollup.config.js b/rollup.config.js index a76f3d6..c488056 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,23 +1,25 @@ import postcss from "rollup-plugin-postcss"; import { terser } from "rollup-plugin-terser"; +import typescript from "@rollup/plugin-typescript"; export default [ { - input: "src/index.js", + input: "src/index.ts", output: [ { file: "dist/index.esm.js", format: "esm", - sourcemap: true + sourcemap: true, }, { file: "dist/index.umd.js", format: "umd", name: "leopard", - sourcemap: true - } + sourcemap: true, + }, ], plugins: [ + typescript(), terser({ output: { comments: (node, comment) => { @@ -26,22 +28,22 @@ export default [ return /license/i.test(comment.value); } return false; - } - } - }) - ] + }, + }, + }), + ], }, { input: "src/index.css", output: { - file: "dist/index.min.css" + file: "dist/index.min.css", }, plugins: [ postcss({ modules: false, extract: true, - minimize: true - }) - ] - } + minimize: true, + }), + ], + }, ]; diff --git a/src/Color.js b/src/Color.ts similarity index 67% rename from src/Color.js rename to src/Color.ts index 674c8ea..4d1dd22 100644 --- a/src/Color.js +++ b/src/Color.ts @@ -1,7 +1,16 @@ -const clamp = (n, min, max) => Math.max(min, Math.min(max, n)); +const clamp = (n: number, min: number, max: number): number => + Math.max(min, Math.min(max, n)); // https://www.rapidtables.com/convert/color/rgb-to-hsv.html -function rgbToHSV(r, g, b) { +function rgbToHSV( + r: number, + g: number, + b: number +): { + h: number; + s: number; + v: number; +} { r /= 255; g /= 255; b /= 255; @@ -26,17 +35,21 @@ function rgbToHSV(r, g, b) { s = delta / max; } - let v = max; + const v = max; return { h: h * 100, s: s * 100, - v: v * 100 + v: v * 100, }; } // https://www.rapidtables.com/convert/color/hsv-to-rgb.html -function hsvToRGB(h, s, v) { +function hsvToRGB( + h: number, + s: number, + v: number +): { r: number; g: number; b: number } { h = (h / 100) * 360; s /= 100; v /= 100; @@ -73,28 +86,43 @@ function hsvToRGB(h, s, v) { return { r: r * 255, g: g * 255, - b: b * 255 + b: b * 255, }; } +/** + * RGBA color, with each component going from 0 to 255. Components may still be decimal. + */ +export type RGBA = [number, number, number, number]; + +/** + * RGBA color, with each component going from 0 to 1. + */ +export type RGBANormalized = [number, number, number, number]; + export default class Color { - constructor(h = 0, s = 0, v = 0, a = 1) { + private _h = 0; + private _s = 0; + private _v = 0; + private _a = 1; + + public constructor(h = 0, s = 0, v = 0, a = 1) { this.h = h; this.s = s; this.v = v; this.a = a; } - static rgb(r, g, b, a = 1) { + public static rgb(r: number, g: number, b: number, a = 1): Color { const { h, s, v } = rgbToHSV(r, g, b); return new Color(h, s, v, a); } - static hsv(h, s, v, a = 1) { + public static hsv(h: number, s: number, v: number, a = 1): Color { return new Color(h, s, v, a); } - static num(n) { + public static num(n: number | string): Color { n = Number(n); // Match Scratch rgba system @@ -107,62 +135,62 @@ export default class Color { } // Red - get r() { + public get r(): number { return hsvToRGB(this.h, this.s, this.v).r; } - set r(r) { + public set r(r) { this._setRGB(r, this.g, this.b); } // Green - get g() { + public get g(): number { return hsvToRGB(this.h, this.s, this.v).g; } - set g(g) { + public set g(g) { this._setRGB(this.r, g, this.b); } // Blue - get b() { + public get b(): number { return hsvToRGB(this.h, this.s, this.v).b; } - set b(b) { + public set b(b) { this._setRGB(this.r, this.g, b); } // Alpha - get a() { + public get a(): number { return this._a; } - set a(a) { + public set a(a) { this._a = clamp(a, 0, 1); } // Hue - get h() { + public get h(): number { return this._h; } - set h(h) { + public set h(h) { this._h = ((h % 100) + 100) % 100; } // Shade - get s() { + public get s(): number { return this._s; } - set s(s) { + public set s(s) { this._s = clamp(s, 0, 100); } // Value - get v() { + public get v(): number { return this._v; } - set v(v) { + public set v(v) { this._v = clamp(v, 0, 100); } - _setRGB(r, g, b) { + private _setRGB(r: number, g: number, b: number): void { r = clamp(r, 0, 255); g = clamp(g, 0, 255); b = clamp(b, 0, 255); @@ -174,8 +202,8 @@ export default class Color { this.v = v; } - toHexString(forceIncludeAlpha = false) { - const toHexDigits = n => { + public toHexString(forceIncludeAlpha = false): string { + const toHexDigits = (n: number): string => { n = clamp(Math.round(n), 0, 255); let str = n.toString(16); @@ -194,7 +222,7 @@ export default class Color { return hex; } - toRGBString(forceIncludeAlpha = false) { + public toRGBString(forceIncludeAlpha = false): string { const rgb = [this.r, this.g, this.b].map(Math.round); if (forceIncludeAlpha || this.a !== 1) { @@ -203,17 +231,17 @@ export default class Color { return `rgb(${rgb.join(", ")})`; } - toRGBA() { + public toRGBA(): RGBA { const rgb = hsvToRGB(this._h, this._s, this._v); return [rgb.r, rgb.g, rgb.b, this._a * 255]; } - toRGBANormalized() { + public toRGBANormalized(): RGBANormalized { const rgb = hsvToRGB(this._h, this._s, this._v); return [rgb.r / 255, rgb.g / 255, rgb.b / 255, this._a]; } - toString() { + public toString(): string { return this.toRGBString(); } } diff --git a/src/Costume.js b/src/Costume.ts similarity index 59% rename from src/Costume.js rename to src/Costume.ts index 5a0bd60..45bca04 100644 --- a/src/Costume.js +++ b/src/Costume.ts @@ -1,5 +1,12 @@ export default class Costume { - constructor(name, url, center = { x: 0, y: 0 }) { + public name: string; + public url: string; + public img: HTMLImageElement; + public isBitmap: boolean; + public resolution: 2 | 1; + public center: { x: number; y: number }; + + public constructor(name: string, url: string, center = { x: 0, y: 0 }) { this.name = name; this.url = url; @@ -14,11 +21,11 @@ export default class Costume { this.center = center; } - get width() { + public get width(): number { return this.img.naturalWidth; } - get height() { + public get height(): number { return this.img.naturalHeight; } } diff --git a/src/Input.js b/src/Input.ts similarity index 68% rename from src/Input.js rename to src/Input.ts index 4f9f207..12ac997 100644 --- a/src/Input.js +++ b/src/Input.ts @@ -1,5 +1,20 @@ +import type { Stage } from "./Sprite"; + +type Mouse = { x: number; y: number; down: boolean }; + export default class Input { - constructor(stage, canvas, onKeyDown) { + private _stage; + private _canvas; + private _onKeyDown; + + public mouse: Mouse; + public keys: string[]; + + public constructor( + stage: Stage, + canvas: HTMLCanvasElement, + onKeyDown: (key: string) => unknown + ) { this._stage = stage; this._canvas = canvas; @@ -20,42 +35,42 @@ export default class Input { this._onKeyDown = onKeyDown; } - _mouseMove(e) { + private _mouseMove(e: MouseEvent): void { const rect = this._canvas.getBoundingClientRect(); const scaleX = this._stage.width / rect.width; const scaleY = this._stage.height / rect.height; const realCoords = { x: (e.clientX - rect.left) * scaleX, - y: (e.clientY - rect.top) * scaleY + y: (e.clientY - rect.top) * scaleY, }; this.mouse = { ...this.mouse, x: realCoords.x - this._stage.width / 2, - y: -realCoords.y + this._stage.height / 2 + y: -realCoords.y + this._stage.height / 2, }; } - _mouseDown() { + private _mouseDown(): void { this.mouse = { ...this.mouse, - down: true + down: true, }; } - _mouseUp() { + private _mouseUp(): void { this.mouse = { ...this.mouse, - down: false + down: false, }; } - _keyup(e) { + private _keyup(e: KeyboardEvent): void { const key = this._getKeyName(e); - this.keys = this.keys.filter(k => k !== key); + this.keys = this.keys.filter((k) => k !== key); } - _keydown(e) { + private _keydown(e: KeyboardEvent): void { e.preventDefault(); const key = this._getKeyName(e); @@ -66,7 +81,7 @@ export default class Input { this._onKeyDown(key); } - _getKeyName(e) { + private _getKeyName(e: KeyboardEvent): string { if (e.key === "ArrowUp") return "up arrow"; if (e.key === "ArrowDown") return "down arrow"; if (e.key === "ArrowLeft") return "left arrow"; @@ -77,12 +92,14 @@ export default class Input { return e.key.toLowerCase(); } - keyPressed(name) { + public keyPressed(name: string): boolean { if (name === "any") return this.keys.length > 0; return this.keys.indexOf(name) > -1; } - focus() { + public focus(): void { this._canvas.focus(); } } + +export type { Mouse }; diff --git a/src/Loudness.js b/src/Loudness.ts similarity index 57% rename from src/Loudness.js rename to src/Loudness.ts index c2aaa7f..c9a6c20 100644 --- a/src/Loudness.js +++ b/src/Loudness.ts @@ -1,39 +1,54 @@ -import Sound from "./Sound.js"; +import Sound from "./Sound"; const IGNORABLE_ERROR = ["NotAllowedError", "NotFoundError"]; +const enum ConnectionState { + /** We have not tried connecting yet. */ + NOT_CONNECTED, + /** We are in the middle of connecting. */ + CONNECTING, + /** We connected successfully. */ + CONNECTED, + /** There was an error connecting. */ + ERROR, +} + // https://github.com/LLK/scratch-audio/blob/develop/src/Loudness.js export default class LoudnessHandler { - constructor() { - // TODO: use a TypeScript enum - this.connectionState = "NOT_CONNECTED"; + private connectionState: ConnectionState; + private audioStream: MediaStream | undefined; + private analyser: AnalyserNode | undefined; + private micDataArray: Float32Array | undefined; + private _lastValue: number | undefined; + + public constructor() { + this.connectionState = ConnectionState.NOT_CONNECTED; } - get audioContext() { + private get audioContext(): AudioContext { return Sound.audioContext; } - async connect() { + private async connect(): Promise { // If we're in the middle of connecting, or failed to connect, // don't attempt to connect again - if (this.connectionState !== "NOT_CONNECTED") return; - this.connectionState = "CONNECTING"; + if (this.connectionState !== ConnectionState.NOT_CONNECTED) return; + this.connectionState = ConnectionState.CONNECTING; try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // Chrome blocks usage of audio until the user interacts with the page. // By calling `resume` here, we will wait until that happens. await Sound.audioContext.resume(); - this.hasConnected = true; this.audioStream = stream; const mic = this.audioContext.createMediaStreamSource(stream); this.analyser = this.audioContext.createAnalyser(); mic.connect(this.analyser); this.micDataArray = new Float32Array(this.analyser.fftSize); - this.connectionState = "CONNECTED"; + this.connectionState = ConnectionState.CONNECTED; } catch (e) { - this.connectionState = "ERROR"; - if (IGNORABLE_ERROR.includes(e.name)) { + this.connectionState = ConnectionState.ERROR; + if (IGNORABLE_ERROR.includes((e as Error).name)) { console.warn("Mic is not available."); } else { throw e; @@ -41,8 +56,13 @@ export default class LoudnessHandler { } } - get loudness() { - if (this.connectionState !== "CONNECTED" || !this.audioStream.active) { + private get loudness(): number { + if ( + this.connectionState !== ConnectionState.CONNECTED || + !this.audioStream?.active || + !this.analyser || + !this.micDataArray + ) { return -1; } @@ -67,8 +87,8 @@ export default class LoudnessHandler { return rms; } - getLoudness() { - this.connect(); + public getLoudness(): number { + void this.connect(); return this.loudness; } } diff --git a/src/Project.js b/src/Project.ts similarity index 63% rename from src/Project.js rename to src/Project.ts index 66d2456..0d1e3e7 100644 --- a/src/Project.js +++ b/src/Project.ts @@ -1,11 +1,36 @@ -import Trigger from "./Trigger.js"; -import Renderer from "./Renderer.js"; -import Input from "./Input.js"; -import LoudnessHandler from "./Loudness.js"; -import Sound from "./Sound.js"; +import Trigger, { TriggerOptions } from "./Trigger"; +import Renderer from "./Renderer"; +import Input from "./Input"; +import LoudnessHandler from "./Loudness"; +import Sound from "./Sound"; +import type { Stage, Sprite } from "./Sprite"; + +type TriggerWithTarget = { + target: Sprite | Stage; + trigger: Trigger; +}; export default class Project { - constructor(stage, sprites = {}, { frameRate = 30 } = {}) { + public stage: Stage; + public sprites: Partial>; + public renderer: Renderer; + public input: Input; + + private loudnessHandler: LoudnessHandler; + private _cachedLoudness: number | null; + + public runningTriggers: TriggerWithTarget[]; + + public answer: string | null; + private timerStart!: Date; + + /** + * Used to keep track of what edge-activated trigger predicates evaluted to + * on the previous step. + */ + private _prevStepTriggerPredicates: WeakMap; + + public constructor(stage: Stage, sprites = {}, { frameRate = 30 } = {}) { this.stage = stage; this.sprites = sprites; @@ -16,9 +41,9 @@ export default class Project { } this.stage._project = this; - this.renderer = new Renderer(this); - this.input = new Input(this.stage, this.renderer.stage, key => { - this.fireTrigger(Trigger.KEY_PRESSED, { key }); + this.renderer = new Renderer(this, null); + this.input = new Input(this.stage, this.renderer.stage, (key) => { + void this.fireTrigger(Trigger.KEY_PRESSED, { key }); }); this.loudnessHandler = new LoudnessHandler(); @@ -26,8 +51,6 @@ export default class Project { this._cachedLoudness = null; this.runningTriggers = []; - // Used to keep track of what edge-activated trigger predicates evaluted to - // on the previous step. this._prevStepTriggerPredicates = new WeakMap(); this.restartTimer(); @@ -43,7 +66,7 @@ export default class Project { this._renderLoop(); } - attach(renderTarget) { + public attach(renderTarget: string | HTMLElement): void { this.renderer.setRenderTarget(renderTarget); this.renderer.stage.addEventListener("click", () => { // Chrome requires a user gesture on the page before we can start the @@ -51,46 +74,48 @@ export default class Project { // When we click the stage, that counts as a user gesture, so try // resuming the audio context. if (Sound.audioContext.state === "suspended") { - Sound.audioContext.resume(); + void Sound.audioContext.resume(); } let clickedSprite = this.renderer.pick(this.spritesAndClones, { x: this.input.mouse.x, - y: this.input.mouse.y + y: this.input.mouse.y, }); if (!clickedSprite) { clickedSprite = this.stage; } - const matchingTriggers = []; + const matchingTriggers: TriggerWithTarget[] = []; for (const trigger of clickedSprite.triggers) { if (trigger.matches(Trigger.CLICKED, {}, clickedSprite)) { matchingTriggers.push({ trigger, target: clickedSprite }); } } - this._startTriggers(matchingTriggers); + void this._startTriggers(matchingTriggers); }); } - greenFlag() { + public greenFlag(): void { // Chrome requires a user gesture on the page before we can start the // audio context. // When greenFlag is triggered, it's likely that the cause of it was some // kind of button click, so try resuming the audio context. if (Sound.audioContext.state === "suspended") { - Sound.audioContext.resume(); + void Sound.audioContext.resume(); } - this.fireTrigger(Trigger.GREEN_FLAG); + void this.fireTrigger(Trigger.GREEN_FLAG); this.input.focus(); } // Find triggers which match the given condition - _matchingTriggers(triggerMatches) { - let matchingTriggers = []; + private _matchingTriggers( + triggerMatches: (tr: Trigger, target: Sprite | Stage) => boolean + ): TriggerWithTarget[] { + const matchingTriggers: TriggerWithTarget[] = []; const targets = this.spritesAndStage; for (const target of targets) { - const matchingTargetTriggers = target.triggers.filter(tr => + const matchingTargetTriggers = target.triggers.filter((tr) => triggerMatches(tr, target) ); for (const match of matchingTargetTriggers) { @@ -100,21 +125,21 @@ export default class Project { return matchingTriggers; } - _stepEdgeActivatedTriggers() { - const edgeActivated = this._matchingTriggers(tr => tr.isEdgeActivated); - const triggersToStart = []; + private _stepEdgeActivatedTriggers(): void { + const edgeActivated = this._matchingTriggers((tr) => tr.isEdgeActivated); + const triggersToStart: TriggerWithTarget[] = []; for (const triggerWithTarget of edgeActivated) { const { trigger, target } = triggerWithTarget; let predicate; switch (trigger.trigger) { case Trigger.TIMER_GREATER_THAN: - predicate = this.timer > trigger.option("VALUE", target); + predicate = this.timer > trigger.option("VALUE", target)!; break; case Trigger.LOUDNESS_GREATER_THAN: - predicate = this.loudness > trigger.option("VALUE", target); + predicate = this.loudness > trigger.option("VALUE", target)!; break; default: - throw new Error(`Unimplemented trigger ${trigger.trigger}`); + throw new Error(`Unimplemented trigger ${String(trigger.trigger)}`); } // Default to false @@ -127,10 +152,10 @@ export default class Project { triggersToStart.push(triggerWithTarget); } } - this._startTriggers(triggersToStart); + void this._startTriggers(triggersToStart); } - step() { + private step(): void { this._cachedLoudness = null; this._stepEdgeActivatedTriggers(); @@ -146,24 +171,26 @@ export default class Project { ); } - render() { + private render(): void { // Render to canvas - this.renderer.update(this.stage, this.spritesAndClones); + this.renderer.update(); // Update watchers - for (const sprite of [...Object.values(this.sprites), this.stage]) { - for (const watcher of Object.values(sprite.watchers)) { - watcher.updateDOM(this.renderer.renderTarget); + if (this.renderer.renderTarget) { + for (const sprite of [...Object.values(this.sprites), this.stage]) { + for (const watcher of Object.values(sprite!.watchers)) { + watcher!.updateDOM(this.renderer.renderTarget); + } } } } - _renderLoop() { + private _renderLoop(): void { requestAnimationFrame(this._renderLoop.bind(this)); this.render(); } - fireTrigger(trigger, options) { + public fireTrigger(trigger: symbol, options?: TriggerOptions): Promise { // Special trigger behaviors if (trigger === Trigger.GREEN_FLAG) { this.restartTimer(); @@ -171,7 +198,7 @@ export default class Project { this.runningTriggers = []; for (const spriteName in this.sprites) { - const sprite = this.sprites[spriteName]; + const sprite = this.sprites[spriteName]!; sprite.clones = []; } @@ -188,14 +215,15 @@ export default class Project { return this._startTriggers(matchingTriggers); } - _startTriggers(triggers) { + // TODO: add a way to start clone triggers from fireTrigger then make this private + public async _startTriggers(triggers: TriggerWithTarget[]): Promise { // Only add these triggers to this.runningTriggers if they're not already there. // TODO: if the triggers are already running, they'll be restarted but their execution order is unchanged. // Does that match Scratch's behavior? for (const trigger of triggers) { if ( !this.runningTriggers.find( - runningTrigger => + (runningTrigger) => trigger.trigger === runningTrigger.trigger && trigger.target === runningTrigger.target ) @@ -203,25 +231,27 @@ export default class Project { this.runningTriggers.push(trigger); } } - return Promise.all( - triggers.map(({ trigger, target }) => { - return trigger.start(target); - }) + await Promise.all( + triggers.map(({ trigger, target }) => trigger.start(target)) ); } - get spritesAndClones() { + public get spritesAndClones(): Sprite[] { return Object.values(this.sprites) - .flatMap(sprite => sprite.andClones()) + .flatMap((sprite) => sprite!.andClones()) .sort((a, b) => a._layerOrder - b._layerOrder); } - get spritesAndStage() { + public get spritesAndStage(): (Sprite | Stage)[] { return [...this.spritesAndClones, this.stage]; } - changeSpriteLayer(sprite, layerDelta, relativeToSprite = sprite) { - let spritesArray = this.spritesAndClones; + public changeSpriteLayer( + sprite: Sprite, + layerDelta: number, + relativeToSprite = sprite + ): void { + const spritesArray = this.spritesAndClones; const originalIndex = spritesArray.indexOf(sprite); const relativeToIndex = spritesArray.indexOf(relativeToSprite); @@ -242,26 +272,26 @@ export default class Project { }); } - stopAllSounds() { + public stopAllSounds(): void { for (const target of this.spritesAndStage) { target.stopAllOfMySounds(); } } - get timer() { - const ms = new Date() - this.timerStart; + public get timer(): number { + const ms = new Date().getTime() - this.timerStart.getTime(); return ms / 1000; } - restartTimer() { + public restartTimer(): void { this.timerStart = new Date(); } - async askAndWait(question) { + public async askAndWait(question: string): Promise { this.answer = await this.renderer.displayAskBox(question); } - get loudness() { + public get loudness(): number { if (this._cachedLoudness === null) { this._cachedLoudness = this.loudnessHandler.getLoudness(); } diff --git a/src/Renderer.js b/src/Renderer.ts similarity index 72% rename from src/Renderer.js rename to src/Renderer.ts index 3b8cede..076e11b 100644 --- a/src/Renderer.js +++ b/src/Renderer.ts @@ -1,14 +1,19 @@ -import Matrix from "./renderer/Matrix.js"; -import Drawable from "./renderer/Drawable.js"; -import BitmapSkin from "./renderer/BitmapSkin.js"; -import PenSkin from "./renderer/PenSkin.js"; -import SpeechBubbleSkin from "./renderer/SpeechBubbleSkin.js"; -import VectorSkin from "./renderer/VectorSkin.js"; -import Rectangle from "./renderer/Rectangle.js"; -import ShaderManager from "./renderer/ShaderManager.js"; -import { effectNames, effectBitmasks } from "./renderer/effectInfo.js"; - -import Costume from "./Costume.js"; +import Matrix, { MatrixType } from "./renderer/Matrix"; +import Drawable from "./renderer/Drawable"; +import BitmapSkin from "./renderer/BitmapSkin"; +import PenSkin from "./renderer/PenSkin"; +import SpeechBubbleSkin from "./renderer/SpeechBubbleSkin"; +import VectorSkin from "./renderer/VectorSkin"; +import Rectangle from "./renderer/Rectangle"; +import ShaderManager, { Shader, DrawMode } from "./renderer/ShaderManager"; +import { effectNames, effectBitmasks } from "./renderer/effectInfo"; +import type Skin from "./renderer/Skin"; + +import Costume from "./Costume"; +import type Color from "./Color"; +import type { RGBANormalized } from "./Color"; +import type Project from "./Project"; +import { Sprite, Stage, _EffectMap, SpeechBubble } from "./Sprite"; // Rectangle used for checking collision bounds. // Rather than create a new one each time, we can just reuse this one. @@ -18,23 +23,59 @@ const __collisionBox = new Rectangle(); // stored in the blue channel, then green, then red. // RGB [0, 0, 0] is reserved for "no sprite here". // This allows for up to 2^24 - 2 different sprites to be rendered at once. -const idToColor = id => [ +const idToColor = (id: number): [number, number, number] => [ (((id + 1) >> 16) & 0xff) / 255, (((id + 1) >> 8) & 0xff) / 255, - ((id + 1) & 0xff) / 255 + ((id + 1) & 0xff) / 255, ]; // Convert a 24-bit color back into a sprite ID/index number. // -1 means "no sprite here". -const colorToId = ([r, g, b]) => ((r << 16) | (g << 8) | b) - 1; +const colorToId = ([r, g, b]: [number, number, number] | Uint8Array): number => + ((r << 16) | (g << 8) | b) - 1; + +type RenderSpriteOptions = { + drawMode: DrawMode; + effectMask?: number; + colorMask?: RGBANormalized; + renderSpeechBubbles?: boolean; + spriteColorId?: (target: Sprite | Stage) => number; +}; + +export type FramebufferInfo = { + texture: WebGLTexture; + width: number; + height: number; + framebuffer: WebGLFramebuffer; +}; export default class Renderer { - constructor(project, renderTarget) { + public project: Project; + public stage: HTMLCanvasElement; + public gl: WebGLRenderingContext; + public renderTarget: HTMLElement | null = null; + + public _shaderManager: ShaderManager; + private _drawables: WeakMap; + private _skins: WeakMap; + + private _currentShader: Shader | null; + private _currentFramebuffer: WebGLFramebuffer | null; + private _screenSpaceScale: number; + private _penSkin: PenSkin; + private _collisionBuffer: FramebufferInfo; + + public constructor( + project: Project, + renderTarget: HTMLElement | string | null + ) { const w = project.stage.width; const h = project.stage.height; this.project = project; - this.stage = this.createStage(w, h); - this.gl = this.stage.getContext("webgl", { antialias: false }); + this.stage = Renderer.createStage(w, h); + const gl = this.stage.getContext("webgl", { antialias: false }); + if (gl === null) throw new Error("Could not initialize WebGL context"); + this.gl = gl; if (renderTarget) { this.setRenderTarget(renderTarget); @@ -51,7 +92,6 @@ export default class Renderer { this._screenSpaceScale = 1; // Initialize a bunch of WebGL state - const gl = this.gl; // Use premultiplied alpha for proper color blending. gl.enable(gl.BLEND); @@ -86,10 +126,9 @@ export default class Renderer { } // Retrieve a given object (e.g. costume or speech bubble)'s skin. If it doesn't exist, make one. - _getSkin(obj) { - if (this._skins.has(obj)) { - return this._skins.get(obj); - } + public _getSkin(obj: Costume | SpeechBubble): Skin { + const existingSkin = this._skins.get(obj); + if (existingSkin) return existingSkin; let skin; @@ -108,10 +147,10 @@ export default class Renderer { } // Retrieve the renderer-specific data object for a given sprite or clone. If it doesn't exist, make one. - _getDrawable(sprite) { - if (this._drawables.has(sprite)) { - return this._drawables.get(sprite); - } + public _getDrawable(sprite: Sprite | Stage): Drawable { + const existingDrawable = this._drawables.get(sprite); + if (existingDrawable) return existingDrawable; + const drawable = new Drawable(this, sprite); this._drawables.set(sprite, drawable); return drawable; @@ -121,10 +160,18 @@ export default class Renderer { // * The framebuffer itself. // * The texture backing the framebuffer. // * The resolution (width and height) of the framebuffer. - _createFramebufferInfo(width, height, filtering, stencil = false) { + public _createFramebufferInfo( + width: number, + height: number, + filtering: + | WebGLRenderingContext["NEAREST"] + | WebGLRenderingContext["LINEAR"], + stencil = false + ): FramebufferInfo { // Create an empty texture with this skin's dimensions. const gl = this.gl; const texture = gl.createTexture(); + if (texture === null) throw new Error("Could not create texture"); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); @@ -142,13 +189,16 @@ export default class Renderer { null ); + const framebuffer = gl.createFramebuffer(); + if (!framebuffer) throw new Error("Could not create framebuffer"); + // Create a framebuffer backed by said texture. This means we can draw onto the framebuffer, // and the results appear in the texture. const framebufferInfo = { texture, width, height, - framebuffer: gl.createFramebuffer() + framebuffer, }; this._setFramebuffer(framebufferInfo); gl.framebufferTexture2D( @@ -176,36 +226,34 @@ export default class Renderer { return framebufferInfo; } - _setShader(shader) { - if (shader !== this._currentShader) { - const gl = this.gl; - gl.useProgram(shader.program); - - // These attributes and uniforms don't ever change, but must be set whenever a new shader program is used. - - const attribLocation = shader.attribs.a_position; - gl.enableVertexAttribArray(attribLocation); - // Bind the 'a_position' vertex attribute to the current contents of `gl.ARRAY_BUFFER`, which in this case - // is a quadrilateral (as buffered earlier). - gl.vertexAttribPointer( - attribLocation, - 2, // every 2 array elements make one vertex. - gl.FLOAT, // data type - false, // normalized - 0, // stride (space between attributes) - 0 // offset (index of the first attribute to start from) - ); + public _setShader(shader: Shader): boolean { + if (shader === this._currentShader) return false; - this._currentShader = shader; - this._updateStageSize(); + const gl = this.gl; + gl.useProgram(shader.program); + + // These attributes and uniforms don't ever change, but must be set whenever a new shader program is used. + + const attribLocation = shader.attribs.a_position; + gl.enableVertexAttribArray(attribLocation); + // Bind the 'a_position' vertex attribute to the current contents of `gl.ARRAY_BUFFER`, which in this case + // is a quadrilateral (as buffered earlier). + gl.vertexAttribPointer( + attribLocation, + 2, // every 2 array elements make one vertex. + gl.FLOAT, // data type + false, // normalized + 0, // stride (space between attributes) + 0 // offset (index of the first attribute to start from) + ); - return true; - } + this._currentShader = shader; + this._updateStageSize(); - return false; + return true; } - _setFramebuffer(framebufferInfo) { + public _setFramebuffer(framebufferInfo: FramebufferInfo | null): void { if (framebufferInfo !== this._currentFramebuffer) { this._currentFramebuffer = framebufferInfo; if (framebufferInfo === null) { @@ -223,37 +271,38 @@ export default class Renderer { } } - setRenderTarget(renderTarget) { + public setRenderTarget(renderTarget: HTMLElement | string | null): void { if (typeof renderTarget === "string") { - renderTarget = document.querySelector(renderTarget); + renderTarget = document.querySelector(renderTarget) as HTMLElement; } this.renderTarget = renderTarget; - this.renderTarget.classList.add("leopard__project"); - this.renderTarget.style.width = `${this.project.stage.width}px`; - this.renderTarget.style.height = `${this.project.stage.height}px`; + if (!renderTarget) return; + renderTarget.classList.add("leopard__project"); + renderTarget.style.width = `${this.project.stage.width}px`; + renderTarget.style.height = `${this.project.stage.height}px`; - this.renderTarget.append(this.stage); + renderTarget.append(this.stage); } // Handles rendering of all layers (including stage, pen layer, sprites, and all clones) in proper order. - _renderLayers(layers, options = {}) { - options = Object.assign( - { - drawMode: ShaderManager.DrawModes.DEFAULT, - renderSpeechBubbles: true - }, - options - ); + private _renderLayers( + layers?: Set, + optionsIn: Partial = {}, + filter?: (layer: Sprite | Stage | PenSkin) => boolean + ): void { + const options = { + drawMode: ShaderManager.DrawModes.DEFAULT, + ...optionsIn, + }; // If we're given a list of layers, filter by that. // If we're given a filter function in the options, filter by that too. // If we're given both, then only include layers which match both. const shouldRestrictLayers = layers instanceof Set; - const shouldFilterLayers = typeof options.filter === "function"; - const shouldIncludeLayer = layer => + const shouldIncludeLayer = (layer: Sprite | Stage | PenSkin): boolean => !( (shouldRestrictLayers && !layers.has(layer)) || - (shouldFilterLayers && !options.filter(layer)) + (filter && !filter(layer)) ); // Stage @@ -289,7 +338,7 @@ export default class Renderer { } } - _updateStageSize() { + private _updateStageSize(): void { if (this._currentShader) { // The shader is passed things in "Scratch-space" (-240, 240) and (-180, 180). // This tells it those dimensions so it can convert them to OpenGL "clip-space" (-1, 1). @@ -311,7 +360,7 @@ export default class Renderer { } // Keep the canvas size in sync with the CSS size. - _resize() { + private _resize(): void { const stageSize = this.stage.getBoundingClientRect(); const ratio = window.devicePixelRatio; const adjustedWidth = Math.round(stageSize.width * ratio); @@ -331,7 +380,7 @@ export default class Renderer { } } - update() { + public update(): void { this._resize(); // Draw to the screen, not to a framebuffer. @@ -345,7 +394,7 @@ export default class Renderer { this._renderLayers(); } - createStage(w, h) { + private static createStage(w: number, h: number): HTMLCanvasElement { const stage = document.createElement("canvas"); stage.width = w; stage.height = h; @@ -365,7 +414,10 @@ export default class Renderer { } // Calculate the transform matrix for a speech bubble attached to a sprite. - _calculateSpeechBubbleMatrix(spr, speechBubbleSkin) { + private _calculateSpeechBubbleMatrix( + spr: Sprite, + speechBubbleSkin: SpeechBubbleSkin + ): MatrixType { const sprBounds = this.getBoundingBox(spr); let x; if ( @@ -388,16 +440,16 @@ export default class Renderer { return m; } - _renderSkin( - skin, - drawMode, - matrix, - scale, - effects, - effectMask, - colorMask, - spriteColorId - ) { + private _renderSkin( + skin: Skin, + drawMode: DrawMode, + matrix: MatrixType, + scale: number, + effects?: _EffectMap, + effectMask?: number, + colorMask?: RGBANormalized, + spriteColorId?: number + ): void { const gl = this.gl; const skinTexture = skin.getTexture(scale * this._screenSpaceScale); @@ -410,16 +462,20 @@ export default class Renderer { this._setShader(shader); gl.uniformMatrix3fv(shader.uniforms.u_transform, false, matrix); - if (effectBitmask !== 0) { + if (effectBitmask !== 0 && effects) { for (const effect of effectNames) { - const effectVal = effects._effectValues[effect]; + const effectVal = effects[effect]; if (effectVal !== 0) gl.uniform1f(shader.uniforms[`u_${effect}`], effectVal); } // Pixelate effect needs the skin size - if (effects._effectValues.pixelate !== 0) - gl.uniform2f(shader.uniforms.u_skinSize, skin.width, skin.height); + if (effects.pixelate !== 0) + gl.uniform2f( + shader.uniforms.u_skinSize, + skin.width ?? 0, + skin.height ?? 0 + ); } gl.bindTexture(gl.TEXTURE_2D, skinTexture); @@ -428,26 +484,27 @@ export default class Renderer { // Enable color masking mode if set if (Array.isArray(colorMask)) - this.gl.uniform4fv(this._currentShader.uniforms.u_colorMask, colorMask); + this.gl.uniform4fv(shader.uniforms.u_colorMask, colorMask); // Used for mapping drawn sprites back to their indices in a list. // By looking at the color of a given pixel, we can tell which sprite is // the topmost one drawn on that pixel. - if (drawMode === ShaderManager.DrawModes.SPRITE_ID && typeof spriteColorId === "number") { - this.gl.uniform3fv( - this._currentShader.uniforms.u_spriteId, - idToColor(spriteColorId) - ); + if ( + drawMode === ShaderManager.DrawModes.SPRITE_ID && + typeof spriteColorId === "number" + ) { + this.gl.uniform3fv(shader.uniforms.u_spriteId, idToColor(spriteColorId)); } // Actually draw the skin this.gl.drawArrays(this.gl.TRIANGLES, 0, 6); } - renderSprite(sprite, options) { - const spriteScale = Object.prototype.hasOwnProperty.call(sprite, "size") - ? sprite.size / 100 - : 1; + private renderSprite( + sprite: Sprite | Stage, + options: RenderSpriteOptions + ): void { + const spriteScale = "size" in sprite ? sprite.size / 100 : 1; this._renderSkin( this._getSkin(sprite.costume), @@ -461,11 +518,15 @@ export default class Renderer { ); if ( - options.renderSpeechBubbles && + options.renderSpeechBubbles !== false && + "_speechBubble" in sprite && sprite._speechBubble && - sprite._speechBubble.text !== "" + sprite._speechBubble.text !== "" && + sprite instanceof Sprite ) { - const speechBubbleSkin = this._getSkin(sprite._speechBubble); + const speechBubbleSkin = this._getSkin( + sprite._speechBubble + ) as SpeechBubbleSkin; this._renderSkin( speechBubbleSkin, @@ -476,16 +537,16 @@ export default class Renderer { } } - getTightBoundingBox(sprite) { + public getTightBoundingBox(sprite: Sprite | Stage): Rectangle { return this._getDrawable(sprite).getTightBoundingBox(); } - getBoundingBox(sprite) { + public getBoundingBox(sprite: Sprite | Stage): Rectangle { return Rectangle.fromMatrix(this._getDrawable(sprite).getMatrix()); } // Mask drawing in to only areas where this sprite is opaque. - _stencilSprite(spr, colorMask) { + private _stencilSprite(spr: Sprite | Stage, colorMask?: Color): void { const gl = this.gl; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); @@ -507,11 +568,16 @@ export default class Renderer { // This, along with the above line, has the effect of not drawing anything to the color buffer, but // creating a "mask" in the stencil buffer that masks out all pixels where this sprite is transparent. - const opts = { + const opts: { + drawMode: DrawMode; + renderSpeechBubbles: boolean; + effectMask: number; + colorMask?: RGBANormalized; + } & RenderSpriteOptions = { drawMode: ShaderManager.DrawModes.SILHOUETTE, renderSpeechBubbles: false, // Ignore ghost effect - effectMask: ~effectBitmasks.ghost + effectMask: ~effectBitmasks.ghost, }; // If we mask in the color (for e.g. "color is touching color"), @@ -530,8 +596,13 @@ export default class Renderer { gl.colorMask(true, true, true, true); } - checkSpriteCollision(spr, targets, fast, sprColor) { - if (!spr.visible) return false; + public checkSpriteCollision( + spr: Sprite | Stage, + targets: Set | (Sprite | Stage)[] | Sprite | Stage, + fast?: boolean, + sprColor?: Color + ): boolean { + if ("visible" in spr && !spr.visible) return false; if (!(targets instanceof Set)) { if (targets instanceof Array) { targets = new Set(targets); @@ -581,7 +652,7 @@ export default class Renderer { this._renderLayers(targets, { drawMode: ShaderManager.DrawModes.SILHOUETTE, // Ignore ghost effect - effectMask: ~effectBitmasks.ghost + effectMask: ~effectBitmasks.ghost, }); const gl = this.gl; @@ -609,7 +680,11 @@ export default class Renderer { return false; } - checkColorCollision(spr, targetsColor, sprColor) { + public checkColorCollision( + spr: Sprite | Stage, + targetsColor: Color, + sprColor?: Color + ): boolean { const sprBox = Rectangle.copy( this.getBoundingBox(spr), __collisionBox @@ -631,9 +706,7 @@ export default class Renderer { this._stencilSprite(spr, sprColor); // Render the sprites to check that we're touching, which will now be masked in to the area of the first sprite. - this._renderLayers(null, { - filter: layer => layer !== spr - }); + this._renderLayers(undefined, undefined, (layer) => layer !== spr); // Make sure to disable the stencil test so as not to affect other rendering! gl.disable(gl.STENCIL_TEST); @@ -667,13 +740,16 @@ export default class Renderer { } // Pick the topmost sprite at the given point (if one exists). - pick(sprites, point) { + public pick( + sprites: (Sprite | Stage)[], + point: { x: number; y: number } + ): Sprite | Stage | null { this._setFramebuffer(this._collisionBuffer); const gl = this.gl; gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); - const spriteIndices = new Map(); + const spriteIndices = new Map(); for (let i = 0; i < sprites.length; i++) { spriteIndices.set(sprites[i], i); } @@ -682,7 +758,7 @@ export default class Renderer { effectMask: ~effectBitmasks.ghost, drawMode: ShaderManager.DrawModes.SPRITE_ID, // let's not use indexOf here--that would be O(n^2) - spriteColorId: (target) => spriteIndices.get(target) + spriteColorId: (target) => spriteIndices.get(target)!, }); const hoveredPixel = new Uint8Array(4); @@ -703,8 +779,12 @@ export default class Renderer { return sprites[index]; } - checkPointCollision(spr, point, fast) { - if (!spr.visible) return false; + public checkPointCollision( + spr: Sprite | Stage, + point: { x: number; y: number }, + fast?: boolean + ): boolean { + if ("visible" in spr && !spr.visible) return false; const box = this.getBoundingBox(spr); if (!box.containsPoint(point.x, point.y)) return false; @@ -733,20 +813,26 @@ export default class Renderer { return hoveredPixel[3] !== 0; } - penLine(pt1, pt2, color, size) { + public penLine( + pt1: { x: number; y: number }, + pt2: { x: number; y: number }, + color: Color, + size: number + ): void { this._penSkin.penLine(pt1, pt2, color, size); } - clearPen() { + public clearPen(): void { this._penSkin.clear(); } - stamp(spr) { + public stamp(spr: Sprite | Stage): void { this._setFramebuffer(this._penSkin._framebufferInfo); this._renderLayers(new Set([spr]), { renderSpeechBubbles: false }); } - displayAskBox(question) { + public displayAskBox(question: string): Promise { + if (!this.renderTarget) return Promise.resolve(""); const askBox = document.createElement("form"); askBox.classList.add("leopard__askBox"); @@ -768,8 +854,8 @@ export default class Renderer { this.renderTarget.append(askBox); askInput.focus(); - return new Promise(resolve => { - askBox.addEventListener("submit", e => { + return new Promise((resolve) => { + askBox.addEventListener("submit", (e) => { e.preventDefault(); askBox.remove(); resolve(askInput.value); diff --git a/src/Sound.js b/src/Sound.ts similarity index 64% rename from src/Sound.js rename to src/Sound.ts index f414372..74f4142 100644 --- a/src/Sound.js +++ b/src/Sound.ts @@ -1,7 +1,20 @@ -import decodeADPCMAudio, { isADPCMData } from "./lib/decode-adpcm-audio.js"; +import decodeADPCMAudio, { isADPCMData } from "./lib/decode-adpcm-audio"; +import type { Yielding } from "./lib/yielding"; export default class Sound { - constructor(name, url) { + public name: string; + public url: string; + + private audioBuffer: AudioBuffer | null; + private source: AudioBufferSourceNode | null; + private playbackRate: number; + private target: AudioNode | undefined; + + private _markDone: (() => void) | undefined; + private _doneDownloading: ((fromMoreRecentCall: boolean) => void) | undefined; + + private static _audioContext: AudioContext | undefined; + public constructor(name: string, url: string) { this.name = name; this.url = url; @@ -10,14 +23,14 @@ export default class Sound { this.playbackRate = 1; // TODO: Remove this line; initiate downloads from somewhere else instead. - this.downloadMyAudioBuffer(); + void this.downloadMyAudioBuffer(); } - get duration() { - return this.audioBuffer.duration; + public get duration(): number { + return this.audioBuffer ? this.audioBuffer.duration : 0; } - *start() { + public *start(): Yielding { let started = false; let isLatestCallToStart = true; @@ -51,7 +64,7 @@ export default class Sound { // finish playing. Of course, the latest call returns true, and so the // containing playUntilDone() (if present) knows to wait. const oldDoneDownloading = this._doneDownloading; - this._doneDownloading = fromMoreRecentCall => { + this._doneDownloading = (fromMoreRecentCall): void => { if (fromMoreRecentCall) { isLatestCallToStart = false; } else { @@ -70,14 +83,14 @@ export default class Sound { return isLatestCallToStart; } - *playUntilDone() { + public *playUntilDone(): Yielding { let playing = true; const isLatestCallToStart = yield* this.start(); // If we failed to download the audio buffer, just stop here - the sound will // never play, so it doesn't make sense to wait for it. - if (!this.audioBuffer) { + if (!this.audioBuffer || !this.source) { return; } @@ -97,7 +110,7 @@ export default class Sound { // is meant to be interrupted if another start() is ran while it's playing. // Of course, we don't want *this* playUntilDone() to be treated as though it // were interrupted when we call start(), so setting _markDone comes after. - this._markDone = () => { + this._markDone = (): void => { playing = false; delete this._markDone; }; @@ -105,7 +118,7 @@ export default class Sound { while (playing) yield; } - stop() { + public stop(): void { if (this._markDone) { this._markDone(); } @@ -116,35 +129,40 @@ export default class Sound { } } - downloadMyAudioBuffer() { + public downloadMyAudioBuffer(): Promise { return fetch(this.url) - .then(body => body.arrayBuffer()) - .then(arrayBuffer => { + .then((body) => body.arrayBuffer()) + .then((arrayBuffer) => { if (isADPCMData(arrayBuffer)) { return decodeADPCMAudio(arrayBuffer, Sound.audioContext).catch( - error => { + (error: Error) => { console.warn( - `Failed to load sound "${this.name}" - will not play:\n` + error + `Failed to load sound "${this.name}" - will not play:\n` + + error.toString() ); return null; } ); } else { - return new Promise((resolve, reject) => { - Sound.audioContext.decodeAudioData(arrayBuffer, resolve, reject); + return new Promise((resolve: DecodeSuccessCallback, reject) => { + void Sound.audioContext.decodeAudioData( + arrayBuffer, + resolve, + reject + ); }); } }) - .then(audioBuffer => { + .then((audioBuffer) => { this.audioBuffer = audioBuffer; if (this._doneDownloading) { - this._doneDownloading(); + this._doneDownloading(false); } return audioBuffer; }); } - playMyAudioBuffer() { + private playMyAudioBuffer(): void { if (!this.audioBuffer) { return; } @@ -164,7 +182,7 @@ export default class Sound { this.source.start(Sound.audioContext.currentTime); } - connect(target) { + public connect(target: AudioNode): void { if (target !== this.target) { this.target = target; if (this.source) { @@ -174,36 +192,174 @@ export default class Sound { } } - setPlaybackRate(value) { + public setPlaybackRate(value: number): void { this.playbackRate = value; if (this.source) { this.source.playbackRate.value = value; } } - isConnectedTo(target) { + public isConnectedTo(target: AudioNode): boolean { return this.target === target; } // Note: "this" refers to the Sound class in static functions. - static get audioContext() { - this._setupAudioContext(); - return this._audioContext; - } - - static _setupAudioContext() { + public static get audioContext(): AudioContext { if (!this._audioContext) { - const AudioContext = window.AudioContext || window.webkitAudioContext; + const AudioContext = + window.AudioContext || + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any + ((window as any).webkitAudioContext as AudioContext); this._audioContext = new AudioContext(); } - } - - static decodeADPCMAudio(audioBuffer) { - return decodeADPCMAudio(audioBuffer, this.audioContext); + return this._audioContext; } } +// Instead of creating a basic Effect class and then implementing a subclass +// for each effect type, we use a simplified object-descriptor style. +// The makeNodes() function returns an object which is passed on to set(), so +// that effects are able to access a variety of nodes (or other values, if +// necessary) required to execute the desired effect. +// +// The code in makeNodes as well as the general definition for each effect is +// all graciously based on LLK's scratch-audio library. +// +// The initial value of an effect should always be the value at which the +// sound is not affected at all - i.e, it would be the same if the effect +// nodes were completely disconnected from the chain or otherwise had never +// been applied. This allows for clean discarding of effect nodes when returned +// to the initial value. +// +// The order of this array matches AudioEngine's effects list in scratch-audio. +// Earlier in the list is closer to the EffectChain input node; later is closer +// to its target (output). Note that a non-"patch" effect's position in the +// array has no bearing on effect behavior, since it isn't part of the chain +// system. +// +// Note that this descriptor list is fairly easy to build on, if we'd like to +// add more audio effects in the future. (Scratch used to have more, but they +// were removed - see commit ff6cd4a - because they depended on an external +// library and were too processor-intensive to support on some devices.) + +type EffectDescriptorBase = { + name: Name; + initial: number; + minimum?: number; + maximum?: number; + resetOnStart?: boolean; + resetOnClone?: boolean; +}; + +type PatchlessDescriptor = { + isPatch: false; + set: (value: number, sound: Sound) => void; +} & EffectDescriptorBase; + +type PatchDescriptor = { + isPatch: true; + makeNodes: () => Nodes & { input: AudioNode; output: AudioNode }; + set: ( + value: number, + nodes: Nodes & { input: AudioNode; output: AudioNode } + ) => void; +} & EffectDescriptorBase; + +type EffectDescriptor< + isPatch extends boolean, + Name extends string, + Nodes extends isPatch extends true ? object : never +> = isPatch extends true + ? PatchDescriptor + : PatchlessDescriptor; + +type Effects = { + [x in EffectName]: number; +}; + +const PanEffect: EffectDescriptor< + true, + "pan", + { leftGain: GainNode; rightGain: GainNode } +> = { + name: "pan", + initial: 0, + minimum: -100, + maximum: 100, + isPatch: true, + makeNodes() { + const aCtx = Sound.audioContext; + const input = aCtx.createGain(); + const leftGain = aCtx.createGain(); + const rightGain = aCtx.createGain(); + const channelMerger = aCtx.createChannelMerger(2); + const output = channelMerger; + input.connect(leftGain); + input.connect(rightGain); + leftGain.connect(channelMerger, 0, 0); + rightGain.connect(channelMerger, 0, 1); + return { input, output, leftGain, rightGain, channelMerger }; + }, + set(value, { leftGain, rightGain }) { + const p = (value + 100) / 200; + const leftVal = Math.cos((p * Math.PI) / 2); + const rightVal = Math.sin((p * Math.PI) / 2); + const { currentTime } = Sound.audioContext; + const { decayWait, decayDuration } = EffectChain; + leftGain.gain.setTargetAtTime( + leftVal, + currentTime + decayWait, + decayDuration + ); + rightGain.gain.setTargetAtTime( + rightVal, + currentTime + decayWait, + decayDuration + ); + }, +} as const; + +const PitchEffect: EffectDescriptor = { + name: "pitch", + initial: 0, + isPatch: false, + set(value, sound) { + const interval = value / 10; + const ratio = Math.pow(2, interval / 12); + sound.setPlaybackRate(ratio); + }, +} as const; + +const VolumeEffect: EffectDescriptor = { + name: "volume", + initial: 100, + minimum: 0, + maximum: 100, + resetOnStart: false, + resetOnClone: true, + isPatch: true, + makeNodes() { + const node = Sound.audioContext.createGain(); + return { + input: node, + output: node, + node, + }; + }, + set(value, { node }) { + node.gain.linearRampToValueAtTime( + value / 100, + Sound.audioContext.currentTime + EffectChain.decayDuration + ); + }, +} as const; + +const effectDescriptors = [PanEffect, PitchEffect, VolumeEffect] as const; +type EffectName = typeof effectDescriptors[number]["name"]; + +type EffectChainConfig = { getNonPatchSoundList: () => Sound[] }; + export class EffectChain { // The code in this class is functionally comparable to the class of the same // name in the scratch-audio library, but is completely rewritten and follows @@ -211,9 +367,19 @@ export class EffectChain { // a portable way to store the effect chain, independent of the audio sources // it affects. - constructor(config) { + public inputNode: AudioNode; + private getNonPatchSoundList: () => Sound[]; + private effectValues!: Record; + private effectNodes: { + [T in typeof effectDescriptors[number] as T["name"]]?: ReturnType< + // eslint-disable-next-line @typescript-eslint/no-explicit-any + T extends PatchDescriptor ? T["makeNodes"] : never + >; + }; + private target?: AudioNode; + + public constructor(config: EffectChainConfig) { const { getNonPatchSoundList } = config; - this.config = config; this.inputNode = Sound.audioContext.createGain(); @@ -232,7 +398,7 @@ export class EffectChain { this.getNonPatchSoundList = getNonPatchSoundList; } - resetToInitial() { + public resetToInitial(): void { // Note: some effects won't be reset by this function, except for when they // are set for the first time (i.e. when the EffectChain is instantiated). // Look for the "reset: false" flag in the effect descriptor list. @@ -241,8 +407,8 @@ export class EffectChain { if (this.effectValues) { for (const [name, initialValue] of Object.entries( EffectChain.getInitialEffectValues() - )) { - if (EffectChain.getEffectDescriptor(name).reset !== false) { + ) as [EffectName, number][]) { + if (EffectChain.getEffectDescriptor(name).resetOnStart !== false) { this.setEffectValue(name, initialValue); } } @@ -251,7 +417,7 @@ export class EffectChain { } } - updateAudioEffect(name) { + private updateAudioEffect(name: EffectName): void { const descriptor = EffectChain.getEffectDescriptor(name); if (!descriptor) { @@ -267,15 +433,22 @@ export class EffectChain { // who have existent nodes. This means we'll skip non-patch effects as // well as effects are set to their initial value. - let next = descriptor; + let nextDescriptor: EffectDescriptorBase = descriptor; do { - next = EffectChain.getNextEffectDescriptor(next.name); - } while (next && !this.effectNodes[next.name]); + nextDescriptor = EffectChain.getNextEffectDescriptor( + nextDescriptor.name + )!; + } while (nextDescriptor && !this.effectNodes[nextDescriptor.name]); - let previous = descriptor; + let previousDescriptor: EffectDescriptorBase = descriptor; do { - previous = EffectChain.getPreviousEffectDescriptor(previous.name); - } while (previous && !this.effectNodes[previous.name]); + previousDescriptor = EffectChain.getPreviousEffectDescriptor( + previousDescriptor.name + )!; + } while ( + previousDescriptor && + !this.effectNodes[previousDescriptor.name] + ); // If we have previous and next values available, they'll currently be // the corresponding descriptors. But we only ever need to access the @@ -283,12 +456,14 @@ export class EffectChain { // with the actual objects containing the effect's nodes here to simplify // later code. - if (next) { - next = this.effectNodes[next.name]; + let next; + if (nextDescriptor) { + next = this.effectNodes[nextDescriptor.name]; } - if (previous) { - next = this.effectNodes[previous.name]; + let previous; + if (previousDescriptor) { + previous = this.effectNodes[previousDescriptor.name]; } // If there is no preceding or following effect which has existent nodes, @@ -321,10 +496,13 @@ export class EffectChain { // node as both its input and output.) Other effects are more complex. // The code in this block controls the actual chaining behavior of // EffectChain, assuring that all effects form a clean chain. - let nodes = this.effectNodes[descriptor.name]; + let nodes = this.effectNodes[descriptor.name]!; if (!nodes && value !== descriptor.initial) { nodes = descriptor.makeNodes(); - this.effectNodes[descriptor.name] = nodes; + // The "as any" cast is needed because TypeScript can't infer that the + // descriptor's name determines the type of its nodes + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any + this.effectNodes[descriptor.name] = nodes as any; // Connect the previous effect, or, if there is none, the EffectChain // input, to this effect. Also disconnect it from whatever it was @@ -370,7 +548,10 @@ export class EffectChain { delete this.effectNodes[name]; } } else { - descriptor.set(value, nodes); + // The "as any" cast is needed because TypeScript can't infer that the + // descriptor's name determines the type of its nodes + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + descriptor.set(value, nodes as any); } } else { // Non-"patch" effects operate directly on Sound objects, accessing @@ -382,7 +563,7 @@ export class EffectChain { } } - connect(target) { + public connect(target: AudioNode): void { this.target = target; // All the code here is basically the same as what's written in @@ -390,13 +571,17 @@ export class EffectChain { // disconnect the final output in the chain - which may be the input // node - and then connect it to the newly specified target. - let last = EffectChain.getLastEffectDescriptor(); + let lastDescriptor: EffectDescriptorBase = + EffectChain.getLastEffectDescriptor(); do { - last = EffectChain.getPreviousEffectDescriptor(last.name); - } while (last && !this.effectNodes[last.name]); - - if (last) { - last = this.effectNodes[last.name]; + lastDescriptor = EffectChain.getPreviousEffectDescriptor( + lastDescriptor.name + )!; + } while (lastDescriptor && !this.effectNodes[lastDescriptor.name]); + + let last; + if (lastDescriptor) { + last = this.effectNodes[lastDescriptor.name]!; } else { last = { output: this.inputNode }; } @@ -405,7 +590,10 @@ export class EffectChain { last.output.connect(target); } - setEffectValue(name, value) { + public setEffectValue( + name: EffectName, + value: number | string | boolean + ): void { value = Number(value); if ( name in this.effectValues && @@ -418,7 +606,10 @@ export class EffectChain { } } - changeEffectValue(name, value) { + private changeEffectValue( + name: EffectName, + value: number | string | boolean + ): void { value = Number(value); if (name in this.effectValues && !isNaN(value) && value !== 0) { this.effectValues[name] += value; @@ -427,79 +618,101 @@ export class EffectChain { } } - clampEffectValue(name) { + private clampEffectValue(name: EffectName): void { // Not all effects are clamped (pitch, for example); it's also possible to // specify only a minimum or maximum bound, instead of both. const descriptor = EffectChain.getEffectDescriptor(name); let value = this.effectValues[name]; - if ("minimum" in descriptor && value < descriptor.minimum) { + if (typeof descriptor.minimum === "number" && value < descriptor.minimum) { value = descriptor.minimum; - } else if ("maximum" in descriptor && value > descriptor.maximum) { + } else if ( + typeof descriptor.maximum === "number" && + value > descriptor.maximum + ) { value = descriptor.maximum; } this.effectValues[name] = value; } - getEffectValue(name) { + public getEffectValue(name: EffectName): number { return this.effectValues[name] || 0; } - clone(newConfig) { + public clone(newConfig: EffectChainConfig): EffectChain { const newEffectChain = new EffectChain( - Object.assign({}, this.config, newConfig) + Object.assign( + { + getNonPatchSoundList: this.getNonPatchSoundList, + }, + newConfig + ) ); - for (const [name, value] of Object.entries(this.effectValues)) { + for (const [name, value] of Object.entries(this.effectValues) as [ + EffectName, + number + ][]) { const descriptor = EffectChain.getEffectDescriptor(name); if (!descriptor.resetOnClone) { newEffectChain.setEffectValue(name, value); } } - newEffectChain.connect(this.target); + if (this.target) newEffectChain.connect(this.target); return newEffectChain; } - applyToSound(sound) { + public applyToSound(sound: Sound): void { sound.connect(this.inputNode); - for (const [name, value] of Object.entries(this.effectValues)) { + for (const [name, value] of Object.entries(this.effectValues) as [ + EffectName, + number + ][]) { const descriptor = EffectChain.getEffectDescriptor(name); if (!descriptor.isPatch) { - descriptor.set(value, sound); + (descriptor as PatchlessDescriptor).set(value, sound); } } } - isTargetOf(sound) { + public isTargetOf(sound: Sound): boolean { return sound.isConnectedTo(this.inputNode); } - static getInitialEffectValues() { + private static getInitialEffectValues(): Record { // This would be an excellent place to use Object.fromEntries, but that // function has been implemented in only the latest of a few modern // browsers. :P - const initials = {}; + const initials: Partial> = {}; for (const { name, initial } of this.effectDescriptors) { initials[name] = initial; } - return initials; + return initials as Record; } - static getEffectDescriptor(name) { - return this.effectDescriptors.find(descriptor => descriptor.name === name); + private static getEffectDescriptor( + name: EffectName + ): typeof EffectChain["effectDescriptors"][number] { + // We know this is non-null because this.effectDescriptors has every effect descriptor in it. + // TODO: use a Record? + return this.effectDescriptors.find( + (descriptor) => descriptor.name === name + )!; } - static getFirstEffectDescriptor() { + private static getFirstEffectDescriptor(): typeof effectDescriptors[number] { return this.effectDescriptors[0]; } - static getLastEffectDescriptor() { + private static getLastEffectDescriptor(): typeof effectDescriptors[number] { return this.effectDescriptors[this.effectDescriptors.length - 1]; } - static getNextEffectDescriptor(name) { + private static getNextEffectDescriptor( + name: EffectName + ): typeof effectDescriptors[number] | undefined { // .find() provides three values to its passed function: the value of the // current item, that item's index, and the array on which .find() is // operating. In this case, we're only concerned with the index. @@ -514,7 +727,9 @@ export class EffectChain { .find((_, i) => this.effectDescriptors[i].name === name); } - static getPreviousEffectDescriptor(name) { + private static getPreviousEffectDescriptor( + name: EffectName + ): typeof effectDescriptors[number] | undefined { // This function's a little simpler, since it doesn't involve shifting the // list. We still use slice(), but this time simply to cut off the last // item; that item will never come before any other, after all. We search @@ -526,130 +741,41 @@ export class EffectChain { .slice(0, -1) .find((_, i) => this.effectDescriptors[i + 1].name === name); } -} -// These are constant values which can be affected to tweak the way effects -// are applied. They match the values used in Scratch 3.0. -EffectChain.decayDuration = 0.025; -EffectChain.decayWait = 0.05; + // These are constant values which can be affected to tweak the way effects + // are applied. They match the values used in Scratch 3.0. + public static decayDuration = 0.025; + public static decayWait = 0.05; -// Instead of creating a basic Effect class and then implementing a subclass -// for each effect type, we use a simplified object-descriptor style. -// The makeNodes() function returns an object which is passed on to set(), so -// that effects are able to access a variety of nodes (or other values, if -// necessary) required to execute the desired effect. -// -// The code in makeNodes as well as the general definition for each effect is -// all graciously based on LLK's scratch-audio library. -// -// The initial value of an effect should always be the value at which the -// sound is not affected at all - i.e, it would be the same if the effect -// nodes were completely disconnected from the chain or otherwise had never -// been applied. This allows for clean discarding of effect nodes when returned -// to the initial value. -// -// The order of this array matches AudioEngine's effects list in scratch-audio. -// Earlier in the list is closer to the EffectChain input node; later is closer -// to its target (output). Note that a non-"patch" effect's position in the -// array has no bearing on effect behavior, since it isn't part of the chain -// system. -// -// Note that this descriptor list is fairly easy to build on, if we'd like to -// add more audio effects in the future. (Scratch used to have more, but they -// were removed - see commit ff6cd4a - because they depended on an external -// library and were too processor-intensive to support on some devices.) -EffectChain.effectDescriptors = [ - { - name: "pan", - initial: 0, - minimum: -100, - maximum: 100, - isPatch: true, - makeNodes() { - const aCtx = Sound.audioContext; - const input = aCtx.createGain(); - const leftGain = aCtx.createGain(); - const rightGain = aCtx.createGain(); - const channelMerger = aCtx.createChannelMerger(2); - const output = channelMerger; - input.connect(leftGain); - input.connect(rightGain); - leftGain.connect(channelMerger, 0, 0); - rightGain.connect(channelMerger, 0, 1); - return { input, output, leftGain, rightGain, channelMerger }; - }, - set(value, { input, output, leftGain, rightGain }) { - const p = (value + 100) / 200; - const leftVal = Math.cos((p * Math.PI) / 2); - const rightVal = Math.sin((p * Math.PI) / 2); - const { currentTime } = Sound.audioContext; - const { decayWait, decayDuration } = EffectChain; - leftGain.gain.setTargetAtTime( - leftVal, - currentTime + decayWait, - decayDuration - ); - rightGain.gain.setTargetAtTime( - rightVal, - currentTime + decayWait, - decayDuration - ); - } - }, - { - name: "pitch", - initial: 0, - isPatch: false, - set(value, sound) { - const interval = value / 10; - const ratio = Math.pow(2, interval / 12); - sound.setPlaybackRate(ratio); - } - }, - { - name: "volume", - initial: 100, - minimum: 0, - maximum: 100, - resetOnStart: false, - resetOnClone: true, - isPatch: true, - makeNodes() { - const node = Sound.audioContext.createGain(); - return { - input: node, - output: node, - node - }; - }, - set(value, { node }) { - node.gain.linearRampToValueAtTime( - value / 100, - Sound.audioContext.currentTime + EffectChain.decayDuration - ); - } - } -]; + public static effectDescriptors = effectDescriptors; +} -export class AudioEffectMap { +export class AudioEffectMap implements Effects { // This class provides a simple interface for setting and getting audio // effects stored on an EffectChain, similar to EffectMap (that class being // for graphic effects). It takes an EffectChain and automatically generates // properties according to the names of the effect descriptors, acting with // the EffectChain's API when accessed. + private effectChain: EffectChain; + + // TypeScript can't infer these + public pan!: number; + public pitch!: number; + public volume!: number; - constructor(effectChain) { + public constructor(effectChain: EffectChain) { this.effectChain = effectChain; for (const { name } of EffectChain.effectDescriptors) { Object.defineProperty(this, name, { get: () => effectChain.getEffectValue(name), - set: value => effectChain.setEffectValue(name, value) + set: (value: string | number | boolean) => + effectChain.setEffectValue(name, value), }); } } - clear() { + public clear(): void { this.effectChain.resetToInitial(); } } diff --git a/src/Sprite.js b/src/Sprite.ts similarity index 59% rename from src/Sprite.js rename to src/Sprite.ts index 26ee11e..9ae6392 100644 --- a/src/Sprite.js +++ b/src/Sprite.ts @@ -1,26 +1,54 @@ -import Color from "./Color.js"; -import Trigger from "./Trigger.js"; -import Sound, { EffectChain, AudioEffectMap } from "./Sound.js"; +import Color from "./Color"; +import Trigger from "./Trigger"; +import Sound, { EffectChain, AudioEffectMap } from "./Sound"; +import Costume from "./Costume"; +import type { Mouse } from "./Input"; +import type Project from "./Project"; +import type Watcher from "./Watcher"; +import type { Yielding } from "./lib/yielding"; + +import { effectNames } from "./renderer/effectInfo"; + +type Effects = { + [x in typeof effectNames[number]]: number; +}; -import { effectNames } from "./renderer/effectInfo.js"; // This is a wrapper to allow the enabled effects in a sprite to be used as a Map key. // By setting an effect, the bitmask is updated as well. // This allows the bitmask to be used to uniquely identify a set of enabled effects. -class _EffectMap { - constructor() { +export class _EffectMap implements Effects { + public _bitmask: number; + private _effectValues: Record; + // TODO: TypeScript can't automatically infer these + public color!: number; + public fisheye!: number; + public whirl!: number; + public pixelate!: number; + public mosaic!: number; + public brightness!: number; + public ghost!: number; + + public constructor() { this._bitmask = 0; - this._effectValues = {}; + this._effectValues = { + color: 0, + fisheye: 0, + whirl: 0, + pixelate: 0, + mosaic: 0, + brightness: 0, + ghost: 0, + }; for (let i = 0; i < effectNames.length; i++) { const effectName = effectNames[i]; - this._effectValues[effectName] = 0; Object.defineProperty(this, effectName, { get: () => { return this._effectValues[effectName]; }, - set: val => { + set: (val: number) => { this._effectValues[effectName] = val; if (val === 0) { @@ -30,31 +58,62 @@ class _EffectMap { // Otherwise, set its bit to 1. this._bitmask = this._bitmask | (1 << i); } - } + }, }); } } - _clone() { + public _clone(): _EffectMap { const m = new _EffectMap(); - for (const effectName of Object.keys(this._effectValues)) { + for (const effectName of Object.keys( + this._effectValues + ) as (keyof typeof this._effectValues)[]) { m[effectName] = this[effectName]; } return m; } - clear() { - for (const effectName of Object.keys(this._effectValues)) { + public clear(): void { + for (const effectName of Object.keys( + this._effectValues + ) as (keyof typeof this._effectValues)[]) { this._effectValues[effectName] = 0; } this._bitmask = 0; } } -class SpriteBase { - constructor(initialConditions, vars = {}) { - this._project = null; +export type SpeechBubbleStyle = "say" | "think"; + +export type SpeechBubble = { + text: string; + style: SpeechBubbleStyle; + timeout: number | null; +}; + +type InitialConditions = { + costumeNumber: number; + layerOrder?: number; +}; + +abstract class SpriteBase { + protected _project!: Project; + + protected _costumeNumber: number; + protected _layerOrder: number; + public triggers: Trigger[]; + public watchers: Partial>; + protected costumes: Costume[]; + protected sounds: Sound[]; + protected effectChain: EffectChain; + public effects: _EffectMap; + public audioEffects: AudioEffectMap; + + protected _vars: object; + + public constructor(initialConditions: InitialConditions, vars = {}) { + // TODO: pass project in here, ideally const { costumeNumber, layerOrder = 0 } = initialConditions; this._costumeNumber = costumeNumber; this._layerOrder = layerOrder; @@ -65,7 +124,7 @@ class SpriteBase { this.sounds = []; this.effectChain = new EffectChain({ - getNonPatchSoundList: this.getSoundsPlayedByMe.bind(this) + getNonPatchSoundList: this.getSoundsPlayedByMe.bind(this), }); this.effectChain.connect(Sound.audioContext.destination); @@ -75,37 +134,47 @@ class SpriteBase { this._vars = vars; } - getSoundsPlayedByMe() { - return this.sounds.filter(sound => this.effectChain.isTargetOf(sound)); + protected getSoundsPlayedByMe(): Sound[] { + return this.sounds.filter((sound) => this.effectChain.isTargetOf(sound)); } - get stage() { + public get stage(): Stage { return this._project.stage; } - get sprites() { + public get sprites(): Partial> { return this._project.sprites; } - get vars() { + public get vars(): object { return this._vars; } - get costumeNumber() { + public get costumeNumber(): number { return this._costumeNumber; } - set costumeNumber(number) { - this._costumeNumber = this.wrapClamp(number, 1, this.costumes.length); - if (this.fireBackdropChanged) this.fireBackdropChanged(); + public set costumeNumber(number) { + if (Number.isFinite(number)) { + this._costumeNumber = this.wrapClamp(number, 1, this.costumes.length); + } else { + this._costumeNumber = 0; + } } - set costume(costume) { + public set costume(costume: number | string | Costume) { + if (costume instanceof Costume) { + const costumeIndex = this.costumes.indexOf(costume); + if (costumeIndex > -1) { + this.costumeNumber = costumeIndex + 1; + } + } if (typeof costume === "number") { this.costumeNumber = costume; + return; } if (typeof costume === "string") { - const index = this.costumes.findIndex(c => c.name === costume); + const index = this.costumes.findIndex((c) => c.name === costume); if (index > -1) { this.costumeNumber = index + 1; } else { @@ -141,7 +210,7 @@ class SpriteBase { } default: { - if (!(isNaN(costume) || costume.trim().length === 0)) { + if (!Number.isNaN(Number(costume)) && costume.trim().length !== 0) { this.costumeNumber = Number(costume); } } @@ -150,52 +219,36 @@ class SpriteBase { } } - get costume() { + public get costume(): Costume { return this.costumes[this.costumeNumber - 1]; } - moveAhead(value = Infinity) { - if (typeof value === "number") { - this._project.changeSpriteLayer(this, value); - } else { - this._project.changeSpriteLayer(this, 1, value); - } - } - - moveBehind(value = Infinity) { - if (typeof value === "number") { - this._project.changeSpriteLayer(this, -value); - } else { - this._project.changeSpriteLayer(this, -1, value); - } - } - - degToRad(deg) { + public degToRad(deg: number): number { return (deg * Math.PI) / 180; } - radToDeg(rad) { + public radToDeg(rad: number): number { return (rad * 180) / Math.PI; } - degToScratch(deg) { + public degToScratch(deg: number): number { return -deg + 90; } - scratchToDeg(scratchDir) { + public scratchToDeg(scratchDir: number): number { return -scratchDir + 90; } - radToScratch(rad) { + public radToScratch(rad: number): number { return this.degToScratch(this.radToDeg(rad)); } - scratchToRad(scratchDir) { + public scratchToRad(scratchDir: number): number { return this.degToRad(this.scratchToDeg(scratchDir)); } // From scratch-vm's math-util. - scratchTan(angle) { + public scratchTan(angle: number): number { angle = angle % 360; switch (angle) { case -270: @@ -210,7 +263,7 @@ class SpriteBase { } // Wrap rotation from -180 to 180. - normalizeDeg(deg) { + public normalizeDeg(deg: number): number { // This is a pretty big math expression, but it's necessary because in JavaScript, // the % operator means "remainder", not "modulo", and so negative numbers won't "wrap around". // See https://web.archive.org/web/20090717035140if_/javascript.about.com/od/problemsolving/a/modulobug.htm @@ -222,13 +275,13 @@ class SpriteBase { // wrapClamp(0, 1, 5) == 5 // wrapClamp(-11, -10, 6) == 6 // Borrowed from scratch-vm (src/util/math-util.js) - wrapClamp(n, min, max) { - const range = (max - min) + 1; - return n - (Math.floor((n - min) / range) * range); + public wrapClamp(n: number, min: number, max: number): number { + const range = max - min + 1; + return n - Math.floor((n - min) / range) * range; } // Given a generator function, return a version of it that runs in "warp mode" (no yields). - warp(procedure) { + public warp(procedure: GeneratorFunction): (...args: unknown[]) => void { const bound = procedure.bind(this); return (...args) => { const inst = bound(...args); @@ -236,7 +289,8 @@ class SpriteBase { }; } - random(a, b) { + // TODO: this should also take strings so rand("0.0", "1.0") returns a random float like Scratch + public random(a: number, b: number): number { const min = Math.min(a, b); const max = Math.max(a, b); if (min % 1 === 0 && max % 1 === 0) { @@ -245,31 +299,31 @@ class SpriteBase { return Math.random() * (max - min) + min; } - *wait(secs) { - let endTime = new Date(); + public *wait(secs: number): Yielding { + const endTime = new Date(); endTime.setMilliseconds(endTime.getMilliseconds() + secs * 1000); while (new Date() < endTime) { yield; } } - get mouse() { + public get mouse(): Mouse { return this._project.input.mouse; } - keyPressed(name) { + public keyPressed(name: string): boolean { return this._project.input.keyPressed(name); } - get timer() { + public get timer(): number { return this._project.timer; } - restartTimer() { + public restartTimer(): void { this._project.restartTimer(); } - *startSound(soundName) { + public *startSound(soundName: string): Yielding { const sound = this.getSound(soundName); if (sound) { this.effectChain.applyToSound(sound); @@ -277,7 +331,7 @@ class SpriteBase { } } - *playSoundUntilDone(soundName) { + public *playSoundUntilDone(soundName: string): Yielding { const sound = this.getSound(soundName); if (sound) { sound.connect(this.effectChain.inputNode); @@ -286,31 +340,31 @@ class SpriteBase { } } - getSound(soundName) { + public getSound(soundName: string): Sound | undefined { if (typeof soundName === "number") { return this.sounds[(soundName - 1) % this.sounds.length]; } else { - return this.sounds.find(s => s.name === soundName); + return this.sounds.find((s) => s.name === soundName); } } - stopAllSounds() { + public stopAllSounds(): void { this._project.stopAllSounds(); } - stopAllOfMySounds() { + public stopAllOfMySounds(): void { for (const sound of this.sounds) { sound.stop(); } } - broadcast(name) { + public broadcast(name: string): Promise { return this._project.fireTrigger(Trigger.BROADCAST, { name }); } - *broadcastAndWait(name) { + public *broadcastAndWait(name: string): Yielding { let running = true; - this.broadcast(name).then(() => { + void this.broadcast(name).then(() => { running = false; }); @@ -319,33 +373,29 @@ class SpriteBase { } } - clearPen() { + public clearPen(): void { this._project.renderer.clearPen(); } - *askAndWait(question) { - if (this._speechBubble) { - this.say(""); - } - + public *askAndWait(question: string): Yielding { let done = false; - this._project.askAndWait(question).then(() => { + void this._project.askAndWait(question).then(() => { done = true; }); while (!done) yield; } - get answer() { + public get answer(): string | null { return this._project.answer; } - get loudness() { + public get loudness(): number { return this._project.loudness; } - toNumber(value) { - if (typeof value === 'number') { + public toNumber(value: unknown): number { + if (typeof value === "number") { if (isNaN(value)) { return 0; } @@ -359,13 +409,13 @@ class SpriteBase { return n; } - toBoolean(value) { - if (typeof value === 'boolean') { + public toBoolean(value: unknown): boolean { + if (typeof value === "boolean") { return value; } - if (typeof value === 'string') { - if (value === '' || value === '0' || value.toLowerCase() === 'false') { + if (typeof value === "string") { + if (value === "" || value === "0" || value.toLowerCase() === "false") { return false; } return true; @@ -374,37 +424,37 @@ class SpriteBase { return Boolean(value); } - toString(value) { + public toString(value: unknown): string { return String(value); } - stringIncludes(string, substring) { + public stringIncludes(string: string, substring: string): boolean { return string.toLowerCase().includes(substring.toLowerCase()); } - arrayIncludes(array, value) { - return array.some(item => this.compare(item, value) === 0); + public arrayIncludes(array: T[], value: T): boolean { + return array.some((item) => this.compare(item, value) === 0); } - letterOf(string, index) { + public letterOf(string: string, index: number): string { if (index < 0 || index >= string.length) { return ""; } return string[index]; } - itemOf(array, index) { + public itemOf(array: T[], index: number): T | "" { if (index < 0 || index >= array.length) { return ""; } return array[index]; } - indexInArray(array, value) { - return array.findIndex(item => this.compare(item, value) === 0); + public indexInArray(array: T[], value: T): number { + return array.findIndex((item) => this.compare(item, value) === 0); } - compare(v1, v2) { + public compare(v1: unknown, v2: unknown): number { if (v1 === v2) { return 0; } @@ -418,9 +468,15 @@ class SpriteBase { return 0; } - if (n1 === 0 && (v1 === null || typeof v1 === 'string' && v1.trim().length === 0)) { + if ( + n1 === 0 && + (v1 === null || (typeof v1 === "string" && v1.trim().length === 0)) + ) { n1 = NaN; - } else if (n2 === 0 && (v2 === null || typeof v2 === 'string' && v2.trim().length === 0)) { + } else if ( + n2 === 0 && + (v2 === null || (typeof v2 === "string" && v2.trim().length === 0)) + ) { n2 = NaN; } @@ -441,9 +497,40 @@ class SpriteBase { } } +type RotationStyle = + typeof Sprite["RotationStyle"][keyof typeof Sprite["RotationStyle"]]; + +type SpriteInitialConditions = { + x: number; + y: number; + direction: number; + rotationStyle?: RotationStyle; + costumeNumber: number; + size: number; + visible: boolean; + penDown?: boolean; + penSize?: number; + penColor?: Color; +}; + export class Sprite extends SpriteBase { - constructor(initialConditions, ...args) { - super(initialConditions, ...args); + private _x: number; + private _y: number; + private _direction: number; + public rotationStyle: RotationStyle; + public size: number; + public visible: boolean; + + private parent: this | null; + public clones: this[]; + + private _penDown: boolean; + public penSize: number; + private _penColor: Color; + public _speechBubble?: SpeechBubble; + + public constructor(initialConditions: SpriteInitialConditions, vars = {}) { + super(initialConditions, vars); const { x, @@ -455,7 +542,7 @@ export class Sprite extends SpriteBase { visible, penDown, penSize, - penColor + penColor, } = initialConditions; this._x = x; @@ -476,20 +563,26 @@ export class Sprite extends SpriteBase { this._speechBubble = { text: "", style: "say", - timeout: null + timeout: null, }; } - createClone() { + public *askAndWait(question: string): Yielding { + if (this._speechBubble) { + this.say(""); + } + + yield* super.askAndWait(question); + } + + public createClone(): void { const clone = Object.assign( - Object.create(Object.getPrototypeOf(this)), + Object.create(Object.getPrototypeOf(this) as object) as this, this ); clone._project = this._project; - clone.triggers = this.triggers.map( - trigger => new Trigger(trigger.trigger, trigger.options, trigger._script) - ); + clone.triggers = this.triggers.map((trigger) => trigger.clone()); clone.costumes = this.costumes; clone.sounds = this.sounds; clone._vars = Object.assign({}, this._vars); @@ -497,19 +590,20 @@ export class Sprite extends SpriteBase { clone._speechBubble = { text: "", style: "say", - timeout: null + timeout: null, }; clone.effects = this.effects._clone(); // Clones inherit audio effects from the original sprite, for some reason. // Couldn't explain it, but that's the behavior in Scratch 3.0. + // eslint-disable-next-line @typescript-eslint/no-this-alias let original = this; while (original.parent) { original = original.parent; } clone.effectChain = original.effectChain.clone({ - getNonPatchSoundList: clone.getSoundsPlayedByMe.bind(clone) + getNonPatchSoundList: clone.getSoundsPlayedByMe.bind(clone), }); // Make a new audioEffects interface which acts on the cloned effect chain. @@ -520,37 +614,37 @@ export class Sprite extends SpriteBase { this.clones.push(clone); // Trigger CLONE_START: - const triggers = clone.triggers.filter(tr => + const triggers = clone.triggers.filter((tr) => tr.matches(Trigger.CLONE_START, {}, clone) ); - this._project._startTriggers( - triggers.map(trigger => ({ trigger, target: clone })) + void this._project._startTriggers( + triggers.map((trigger) => ({ trigger, target: clone })) ); } - deleteThisClone() { + public deleteThisClone(): void { if (this.parent === null) return; - this.parent.clones = this.parent.clones.filter(clone => clone !== this); + this.parent.clones = this.parent.clones.filter((clone) => clone !== this); this._project.runningTriggers = this._project.runningTriggers.filter( ({ target }) => target !== this ); } - andClones() { - return [this, ...this.clones.flatMap(clone => clone.andClones())]; + public andClones(): this[] { + return [this, ...this.clones.flatMap((clone) => clone.andClones())]; } - get direction() { + public get direction(): number { return this._direction; } - set direction(dir) { + public set direction(dir) { this._direction = this.normalizeDeg(dir); } - goto(x, y) { + public goto(x: number, y: number): void { if (x === this.x && y === this.y) return; if (this.penDown) { @@ -566,23 +660,23 @@ export class Sprite extends SpriteBase { this._y = y; } - get x() { + public get x(): number { return this._x; } - set x(x) { + public set x(x) { this.goto(x, this._y); } - get y() { + public get y(): number { return this._y; } - set y(y) { + public set y(y) { this.goto(this._x, y); } - move(dist) { + public move(dist: number): void { const moveDir = this.scratchToRad(this.direction); this.goto( @@ -591,8 +685,9 @@ export class Sprite extends SpriteBase { ); } - *glide(seconds, x, y) { - const interpolate = (a, b, t) => a + (b - a) * t; + public *glide(seconds: number, x: number, y: number): Yielding { + const interpolate = (a: number, b: number, t: number): number => + a + (b - a) * t; const startTime = new Date(); const startX = this._x; @@ -600,7 +695,7 @@ export class Sprite extends SpriteBase { let t; do { - t = (new Date() - startTime) / (seconds * 1000); + t = (new Date().getTime() - startTime.getTime()) / (seconds * 1000); this.goto(interpolate(startX, x, t), interpolate(startY, y, t)); yield; } while (t < 1); @@ -653,11 +748,27 @@ export class Sprite extends SpriteBase { this.goto(this.x + dx, this.y + dy); } - get penDown() { + public moveAhead(value: number | Sprite = Infinity): void { + if (typeof value === "number") { + this._project.changeSpriteLayer(this, value); + } else { + this._project.changeSpriteLayer(this, 1, value); + } + } + + public moveBehind(value: number | Sprite = Infinity): void { + if (typeof value === "number") { + this._project.changeSpriteLayer(this, -value); + } else { + this._project.changeSpriteLayer(this, -1, value); + } + } + + public get penDown(): boolean { return this._penDown; } - set penDown(penDown) { + public set penDown(penDown) { if (penDown) { this._project.renderer.penLine( { x: this.x, y: this.y }, @@ -669,25 +780,28 @@ export class Sprite extends SpriteBase { this._penDown = penDown; } - get penColor() { + public get penColor(): Color { return this._penColor; } - set penColor(color) { + public set penColor(color: unknown) { if (color instanceof Color) { this._penColor = color; } else { console.error( - `${color} is not a valid penColor. Try using the Color class!` + `${String(color)} is not a valid penColor. Try using the Color class!` ); } } - stamp() { + public stamp(): void { this._project.renderer.stamp(this); } - touching(target, fast = false) { + public touching( + target: "mouse" | "edge" | Sprite | Stage, + fast = false + ): boolean { if (typeof target === "string") { switch (target) { case "mouse": @@ -695,7 +809,7 @@ export class Sprite extends SpriteBase { this, { x: this.mouse.x, - y: this.mouse.y + y: this.mouse.y, }, fast ); @@ -712,6 +826,7 @@ export class Sprite extends SpriteBase { } default: console.error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot find target "${target}" in "touching". Did you mean to pass a sprite class instead?` ); return false; @@ -723,9 +838,10 @@ export class Sprite extends SpriteBase { return this._project.renderer.checkSpriteCollision(this, target, fast); } - colorTouching(color, target) { + public colorTouching(color: Color, target: Sprite | Stage): boolean { if (typeof target === "string") { console.error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot find target "${target}" in "touchingColor". Did you mean to pass a sprite class instead?` ); return false; @@ -733,6 +849,7 @@ export class Sprite extends SpriteBase { if (typeof color === "string") { console.error( + // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Cannot find color "${color}" in "touchingColor". Did you mean to pass a Color instance instead?` ); return false; @@ -760,7 +877,7 @@ export class Sprite extends SpriteBase { const distRight = Math.max(0, stageWidth / 2 - bounds.right); const distBottom = Math.max(0, stageHeight / 2 + bounds.bottom); // Find the nearest edge. - let nearestEdge = ""; + let nearestEdge = null; let minDist = Infinity; if (distLeft < minDist) { minDist = distLeft; @@ -784,73 +901,92 @@ export class Sprite extends SpriteBase { return nearestEdge; } - say(text) { - clearTimeout(this._speechBubble.timeout); + public say(text: string): void { + if (this._speechBubble?.timeout) clearTimeout(this._speechBubble.timeout); this._speechBubble = { text: String(text), style: "say", timeout: null }; } - think(text) { - clearTimeout(this._speechBubble.timeout); + public think(text: string): void { + if (this._speechBubble?.timeout) clearTimeout(this._speechBubble.timeout); this._speechBubble = { text: String(text), style: "think", timeout: null }; } - *sayAndWait(text, seconds) { - clearTimeout(this._speechBubble.timeout); + public *sayAndWait(text: string, seconds: number): Yielding { + if (this._speechBubble?.timeout) clearTimeout(this._speechBubble.timeout); + const speechBubble: SpeechBubble = { text, style: "say", timeout: null }; let done = false; - const timeout = setTimeout(() => { - this._speechBubble.text = ""; - this.timeout = null; + const timeout = window.setTimeout(() => { + speechBubble.text = ""; + speechBubble.timeout = null; done = true; }, seconds * 1000); - this._speechBubble = { text, style: "say", timeout }; + speechBubble.timeout = timeout; + this._speechBubble = speechBubble; while (!done) yield; } - *thinkAndWait(text, seconds) { - clearTimeout(this._speechBubble.timeout); + public *thinkAndWait(text: string, seconds: number): Yielding { + if (this._speechBubble?.timeout) clearTimeout(this._speechBubble.timeout); + const speechBubble: SpeechBubble = { text, style: "think", timeout: null }; let done = false; - const timeout = setTimeout(() => { - this._speechBubble.text = ""; - this.timeout = null; + const timeout = window.setTimeout(() => { + speechBubble.text = ""; + speechBubble.timeout = null; done = true; }, seconds * 1000); - this._speechBubble = { text, style: "think", timeout }; + speechBubble.timeout = timeout; + this._speechBubble = speechBubble; while (!done) yield; } -} -Sprite.RotationStyle = Object.freeze({ - ALL_AROUND: Symbol("ALL_AROUND"), - LEFT_RIGHT: Symbol("LEFT_RIGHT"), - DONT_ROTATE: Symbol("DONT_ROTATE") -}); + public static RotationStyle = Object.freeze({ + ALL_AROUND: Symbol("ALL_AROUND"), + LEFT_RIGHT: Symbol("LEFT_RIGHT"), + DONT_ROTATE: Symbol("DONT_ROTATE"), + }); + + public static Edge = Object.freeze({ + BOTTOM: Symbol("BOTTOM"), + LEFT: Symbol("LEFT"), + RIGHT: Symbol("RIGHT"), + TOP: Symbol("TOP") + }); +} -Sprite.Edge = Object.freeze({ - BOTTOM: Symbol("BOTTOM"), - LEFT: Symbol("LEFT"), - RIGHT: Symbol("RIGHT"), - TOP: Symbol("TOP") -}); +type StageInitialConditions = { + width?: number; + height?: number; +} & InitialConditions; export class Stage extends SpriteBase { - constructor(initialConditions, ...args) { - super(initialConditions, ...args); + public readonly width!: number; + public readonly height!: number; + public __counter: number; + public fence: { + left: number; + right: number; + top: number; + bottom: number; + } + + public constructor(initialConditions: StageInitialConditions, vars = {}) { + super(initialConditions, vars); // Use defineProperties to make these non-writable. // Changing the width and height of the stage after initialization isn't supported. Object.defineProperties(this, { width: { value: initialConditions.width || 480, - enumerable: true + enumerable: true, }, height: { value: initialConditions.height || 360, - enumerable: true - } + enumerable: true, + }, }); this.fence = { @@ -860,15 +996,22 @@ export class Stage extends SpriteBase { bottom: -this.height / 2 }; - this.name = "Stage"; - // For obsolete counter blocks. this.__counter = 0; } - fireBackdropChanged() { + public fireBackdropChanged(): Promise { return this._project.fireTrigger(Trigger.BACKDROP_CHANGED, { - backdrop: this.costume.name + backdrop: this.costume.name, }); } + + public get costumeNumber(): number { + return super.costumeNumber; + } + + public set costumeNumber(number) { + super.costumeNumber = number; + void this.fireBackdropChanged(); + } } diff --git a/src/Trigger.js b/src/Trigger.js deleted file mode 100644 index fcfe512..0000000 --- a/src/Trigger.js +++ /dev/null @@ -1,99 +0,0 @@ -const GREEN_FLAG = Symbol("GREEN_FLAG"); -const KEY_PRESSED = Symbol("KEY_PRESSED"); -const BROADCAST = Symbol("BROADCAST"); -const CLICKED = Symbol("CLICKED"); -const CLONE_START = Symbol("CLONE_START"); -const LOUDNESS_GREATER_THAN = Symbol("LOUDNESS_GREATER_THAN"); -const TIMER_GREATER_THAN = Symbol("TIMER_GREATER_THAN"); -const BACKDROP_CHANGED = Symbol("BACKDROP_CHANGED"); - -export default class Trigger { - constructor(trigger, options, script) { - this.trigger = trigger; - - if (typeof script === "undefined") { - this.options = {}; - this._script = options; - } else { - this.options = options; - this._script = script; - } - - this.done = false; - this.stop = () => {}; - } - - get isEdgeActivated() { - return ( - this.trigger === TIMER_GREATER_THAN || - this.trigger === LOUDNESS_GREATER_THAN - ); - } - - // Evaluate the given trigger option, whether it's a value or a function that - // returns a value given a target - option(option, target) { - let triggerOption = this.options[option]; - // If the given option is a function, evaluate that function, passing in - // the target that we're evaluating the trigger for - if (typeof triggerOption === "function") { - return triggerOption(target); - } - return triggerOption; - } - - matches(trigger, options, target) { - if (this.trigger !== trigger) return false; - for (let option in options) { - if (this.option(option, target) !== options[option]) return false; - } - - return true; - } - - start(target) { - this.stop(); - - const boundScript = this._script.bind(target); - - this.done = false; - this._runningScript = boundScript(); - - return new Promise(resolve => { - this.stop = () => { - this.done = true; - resolve(); - }; - }); - } - - step() { - this.done = this._runningScript.next().done; - if (this.done) this.stop(); - } - - static get GREEN_FLAG() { - return GREEN_FLAG; - } - static get KEY_PRESSED() { - return KEY_PRESSED; - } - static get BROADCAST() { - return BROADCAST; - } - static get CLICKED() { - return CLICKED; - } - static get CLONE_START() { - return CLONE_START; - } - static get LOUDNESS_GREATER_THAN() { - return LOUDNESS_GREATER_THAN; - } - static get TIMER_GREATER_THAN() { - return TIMER_GREATER_THAN; - } - static get BACKDROP_CHANGED() { - return BACKDROP_CHANGED; - } -} diff --git a/src/Trigger.ts b/src/Trigger.ts new file mode 100644 index 0000000..98a82d7 --- /dev/null +++ b/src/Trigger.ts @@ -0,0 +1,116 @@ +import type { Sprite, Stage } from "./Sprite"; + +type TriggerOption = + | number + | string + | boolean + | ((target: Sprite | Stage) => number | string | boolean); + +type TriggerOptions = Partial>; + +export default class Trigger { + public trigger; + private options: TriggerOptions; + private _script: GeneratorFunction; + private _runningScript: Generator | undefined; + public done: boolean; + private stop: () => void; + + public constructor( + trigger: symbol, + options: TriggerOptions, + script?: GeneratorFunction + ); + public constructor(trigger: symbol, script: GeneratorFunction); + public constructor( + trigger: symbol, + optionsOrScript: TriggerOptions | GeneratorFunction, + script?: GeneratorFunction + ) { + this.trigger = trigger; + + if (typeof script === "undefined") { + this.options = {}; + this._script = optionsOrScript as GeneratorFunction; + } else { + this.options = optionsOrScript as TriggerOptions; + this._script = script; + } + + this.done = false; + // eslint-disable-next-line @typescript-eslint/no-empty-function + this.stop = () => {}; + } + + public get isEdgeActivated(): boolean { + return ( + this.trigger === Trigger.TIMER_GREATER_THAN || + this.trigger === Trigger.LOUDNESS_GREATER_THAN + ); + } + + // Evaluate the given trigger option, whether it's a value or a function that + // returns a value given a target + public option( + option: string, + target: Sprite | Stage + ): number | string | boolean | undefined { + const triggerOption = this.options[option]; + // If the given option is a function, evaluate that function, passing in + // the target that we're evaluating the trigger for + if (typeof triggerOption === "function") { + return triggerOption(target); + } + return triggerOption; + } + + public matches( + trigger: Trigger["trigger"], + options: Trigger["options"] | undefined, + target: Sprite | Stage + ): boolean { + if (this.trigger !== trigger) return false; + for (const option in options) { + if (this.option(option, target) !== options[option]) return false; + } + + return true; + } + + public start(target: Sprite | Stage): Promise { + this.stop(); + + this.done = false; + this._runningScript = this._script.call(target); + + return new Promise((resolve) => { + this.stop = (): void => { + this.done = true; + resolve(); + }; + }); + } + + public step(): void { + if (!this._runningScript) return; + this.done = !!this._runningScript.next().done; + if (this.done) this.stop(); + } + + public clone(): Trigger { + return new Trigger(this.trigger, this.options, this._script); + } + + public static readonly GREEN_FLAG = Symbol("GREEN_FLAG"); + public static readonly KEY_PRESSED = Symbol("KEY_PRESSED"); + public static readonly BROADCAST = Symbol("BROADCAST"); + public static readonly CLICKED = Symbol("CLICKED"); + public static readonly CLONE_START = Symbol("CLONE_START"); + public static readonly LOUDNESS_GREATER_THAN = Symbol( + "LOUDNESS_GREATER_THAN" + ); + public static readonly TIMER_GREATER_THAN = Symbol("TIMER_GREATER_THAN"); + public static readonly BACKDROP_CHANGED = Symbol("BACKDROP_CHANGED"); +} + +export type { TriggerOption, TriggerOptions }; diff --git a/src/Watcher.js b/src/Watcher.ts similarity index 63% rename from src/Watcher.js rename to src/Watcher.ts index bb9d5f5..389d82f 100644 --- a/src/Watcher.js +++ b/src/Watcher.ts @@ -1,8 +1,57 @@ import Color from "./Color"; +type WatcherValue = + | string + | number + | boolean + | null + | undefined + | (string | number | boolean | null | undefined)[]; + +type WatcherStyle = "normal" | "large" | "slider"; + +type WatcherOptions = { + value?: () => WatcherValue; + setValue?: (value: number) => void; + label: string; + style?: WatcherStyle; + visible?: boolean; + color?: Color; + step?: number; + x?: number; + y?: number; + width?: number; + height?: number; + min?: number; + max?: number; +}; + export default class Watcher { - constructor({ + public value: () => WatcherValue; + public setValue: (value: number) => void; + private _previousValue: unknown | symbol; + private color: Color; + private _label!: string; + private _x!: number; + private _y!: number; + private _width: number | undefined; + private _height: number | undefined; + private _min!: number; + private _max!: number; + private _step!: number; + private _style!: WatcherStyle; + private _visible!: boolean; + + private _dom!: { + node: HTMLElement; + label: HTMLElement; + value: HTMLElement; + slider: HTMLInputElement; + }; + + public constructor({ value = () => "", + // eslint-disable-next-line @typescript-eslint/no-empty-function setValue = () => {}, label, style = "normal", @@ -14,8 +63,8 @@ export default class Watcher { x = -240, y = 180, width, - height - }) { + height, + }: WatcherOptions) { this.initializeDOM(); this.value = value; @@ -36,7 +85,7 @@ export default class Watcher { this.height = height; } - initializeDOM() { + private initializeDOM(): void { const node = document.createElement("div"); node.classList.add("leopard__watcher"); @@ -52,8 +101,8 @@ export default class Watcher { slider.type = "range"; slider.classList.add("leopard__watcherSlider"); - slider.addEventListener("input", event => { - this.setValue(Number(event.target.value)); + slider.addEventListener("input", () => { + this.setValue(Number(slider.value)); }); node.append(slider); @@ -61,7 +110,7 @@ export default class Watcher { this._dom = { node, label, value, slider }; } - updateDOM(renderTarget) { + public updateDOM(renderTarget: HTMLElement | null): void { if (renderTarget && !renderTarget.contains(this._dom.node)) { renderTarget.append(this._dom.node); } @@ -86,11 +135,11 @@ export default class Watcher { const indexElem = document.createElement("div"); indexElem.classList.add("leopard__watcherListItemIndex"); - indexElem.innerText = index; + indexElem.innerText = String(index); const contentElem = document.createElement("div"); contentElem.classList.add("leopard__watcherListItemContent"); - contentElem.innerText = item.toString(); + contentElem.innerText = String(item); itemElem.append(indexElem); itemElem.append(contentElem); @@ -100,7 +149,7 @@ export default class Watcher { } else { // Render like a normal variable if (value !== this._previousValue) { - this._dom.value.innerText = value.toString(); + this._dom.value.innerText = String(value); } } @@ -112,7 +161,8 @@ export default class Watcher { // Set slider value if (this._style === "slider") { - this._dom.slider.value = value; + // TODO: handle non-numeric slider values + this._dom.slider.value = String(value); } // Update color @@ -126,58 +176,58 @@ export default class Watcher { this._dom.value.style.setProperty("--watcher-text-color", textColor); } - get visible() { + public get visible(): boolean { return this._visible; } - set visible(visible) { + public set visible(visible) { this._visible = visible; this._dom.node.style.visibility = visible ? "visible" : "hidden"; } - get x() { + public get x(): number { return this._x; } - set x(x) { + public set x(x) { this._x = x; this._dom.node.style.left = `${x - 240}px`; } - get y() { + public get y(): number { return this._y; } - set y(y) { + public set y(y) { this._y = y; this._dom.node.style.top = `${180 - y}px`; } - get width() { + public get width(): number | undefined { return this._width; } - set width(width) { + public set width(width) { this._width = width; if (width) { this._dom.node.style.width = `${width}px`; } else { - this._dom.node.style.width = undefined; + this._dom.node.style.removeProperty("width"); } } - get height() { + public get height(): number | undefined { return this._height; } - set height(height) { + public set height(height) { this._height = height; if (height) { this._dom.node.style.height = `${height}px`; } else { - this._dom.node.style.height = undefined; + this._dom.node.style.removeProperty("height"); } } - get style() { + public get style(): WatcherStyle { return this._style; } - set style(style) { + public set style(style) { this._style = style; this._dom.node.classList.toggle( "leopard__watcher--normal", @@ -193,34 +243,34 @@ export default class Watcher { ); } - get min() { + public get min(): number { return this._min; } - set min(min) { + public set min(min: number) { this._min = min; - this._dom.slider.min = min; + this._dom.slider.min = String(min); } - get max() { + public get max(): number { return this._max; } - set max(max) { + public set max(max: number) { this._max = max; - this._dom.slider.max = max; + this._dom.slider.max = String(max); } - get step() { + public get step(): number { return this._step; } - set step(step) { + public set step(step) { this._step = step; - this._dom.slider.step = step; + this._dom.slider.step = String(step); } - get label() { + public get label(): string { return this._label; } - set label(label) { + public set label(label) { this._label = label; this._dom.label.innerText = label; } diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 09c7210..0000000 --- a/src/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import Project from "./Project.js"; -import { Sprite, Stage } from "./Sprite.js"; -import Trigger from "./Trigger.js"; -import Watcher from "./Watcher.js"; -import Costume from "./Costume.js"; -import Color from "./Color.js"; -import Sound from "./Sound.js"; - -export { Project, Sprite, Stage, Trigger, Watcher, Costume, Color, Sound }; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..db118dc --- /dev/null +++ b/src/index.ts @@ -0,0 +1,9 @@ +import Project from "./Project"; +import { Sprite, Stage } from "./Sprite"; +import Trigger from "./Trigger"; +import Watcher from "./Watcher"; +import Costume from "./Costume"; +import Color from "./Color"; +import Sound from "./Sound"; + +export { Project, Sprite, Stage, Trigger, Watcher, Costume, Color, Sound }; diff --git a/src/lib/decode-adpcm-audio.js b/src/lib/decode-adpcm-audio.ts similarity index 87% rename from src/lib/decode-adpcm-audio.js rename to src/lib/decode-adpcm-audio.ts index c971d1a..a7fc8f0 100644 --- a/src/lib/decode-adpcm-audio.js +++ b/src/lib/decode-adpcm-audio.ts @@ -32,14 +32,17 @@ const ADPCM_STEPS = [ const ADPCM_INDEX = [-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8]; -export default function decodeADPCMAudio(ab, audioContext) { +export default function decodeADPCMAudio( + ab: ArrayBuffer, + audioContext: AudioContext +): Promise { const dv = new DataView(ab); // WAV magic number if (dv.getUint32(0) !== 0x52494646 || dv.getUint32(8) !== 0x57415645) { return Promise.reject(new Error("Unrecognized audio format")); } - const blocks = {}; + const blocks: Partial> = {}; const l = dv.byteLength - 8; let i = 12; while (i < l) { @@ -54,6 +57,12 @@ export default function decodeADPCMAudio(ab, audioContext) { i += 8 + dv.getUint32(i + 4, true); } + const factBlock = blocks.fact; + const dataBlock = blocks.data; + if (typeof factBlock !== "number" || typeof dataBlock !== "number") { + return Promise.reject(new Error("Invalid WAV")); + } + const format = dv.getUint16(20, true); const sampleRate = dv.getUint32(24, true); @@ -61,17 +70,17 @@ export default function decodeADPCMAudio(ab, audioContext) { const samplesPerBlock = dv.getUint16(38, true); const blockSize = (samplesPerBlock - 1) / 2 + 4; - const frameCount = dv.getUint32(blocks.fact + 8, true); + const frameCount = dv.getUint32(factBlock + 8, true); const buffer = audioContext.createBuffer(1, frameCount, sampleRate); const channel = buffer.getChannelData(0); - let sample; + let sample = 0; let index = 0; let step, code, delta; let lastByte = -1; - const offset = blocks.data + 8; + const offset = dataBlock + 8; let i = offset; let j = 0; // eslint-disable-next-line @@ -115,14 +124,14 @@ export default function decodeADPCMAudio(ab, audioContext) { return Promise.reject(new Error(`Unrecognized WAV format ${format}`)); } -export function isWavData(arrayBuffer) { +export function isWavData(arrayBuffer: ArrayBuffer): boolean { const dataView = new DataView(arrayBuffer); return ( dataView.getUint32(0) === 0x52494646 && dataView.getUint32(8) === 0x57415645 ); } -export function isADPCMData(arrayBuffer) { +export function isADPCMData(arrayBuffer: ArrayBuffer): boolean { const dataView = new DataView(arrayBuffer); const format = dataView.getUint16(20, true); return isWavData(arrayBuffer) && format === 17; diff --git a/src/lib/yielding.ts b/src/lib/yielding.ts new file mode 100644 index 0000000..317eb8b --- /dev/null +++ b/src/lib/yielding.ts @@ -0,0 +1,7 @@ +/** + * Utility type for a generator function that yields nothing until eventually + * resolving to a value. Used extensively in Leopard and defined here so we + * don't have to type out the full definition each time (and also so I don't + * have to go back and change it everywhere if this type turns out to be wrong). + */ +export type Yielding = Generator; diff --git a/src/renderer/BitmapSkin.js b/src/renderer/BitmapSkin.ts similarity index 74% rename from src/renderer/BitmapSkin.js rename to src/renderer/BitmapSkin.ts index 399508e..de6be8f 100644 --- a/src/renderer/BitmapSkin.js +++ b/src/renderer/BitmapSkin.ts @@ -1,7 +1,12 @@ -import Skin from "./Skin.js"; +import type Renderer from "../Renderer"; +import Skin from "./Skin"; export default class BitmapSkin extends Skin { - constructor(renderer, image) { + private _image: HTMLImageElement; + private _imageData: ImageData | null; + private _texture: WebGLTexture | null; + + public constructor(renderer: Renderer, image: HTMLImageElement) { super(renderer); this._image = image; @@ -11,7 +16,7 @@ export default class BitmapSkin extends Skin { this._setSizeFromImage(image); } - getImageData() { + public getImageData(): ImageData | null { // Make sure to handle potentially non-loaded textures if (!this._image.complete) return null; @@ -20,6 +25,7 @@ export default class BitmapSkin extends Skin { canvas.width = this._image.naturalWidth || this._image.width; canvas.height = this._image.naturalHeight || this._image.height; const ctx = canvas.getContext("2d"); + if (!ctx) return null; ctx.drawImage(this._image, 0, 0); // Cache image data so we can reuse it this._imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -28,7 +34,7 @@ export default class BitmapSkin extends Skin { return this._imageData; } - getTexture() { + public getTexture(): WebGLTexture | null { // Make sure to handle potentially non-loaded textures const image = this._image; if (!image.complete) return null; @@ -40,7 +46,7 @@ export default class BitmapSkin extends Skin { return this._texture; } - destroy() { + public destroy(): void { if (this._texture !== null) this.gl.deleteTexture(this._texture); } } diff --git a/src/renderer/Drawable.js b/src/renderer/Drawable.ts similarity index 76% rename from src/renderer/Drawable.js rename to src/renderer/Drawable.ts index d2a7819..de28a10 100644 --- a/src/renderer/Drawable.js +++ b/src/renderer/Drawable.ts @@ -1,15 +1,21 @@ -import Matrix from "./Matrix.js"; +import Matrix, { MatrixType } from "./Matrix"; -import Rectangle from "./Rectangle.js"; -import effectTransformPoint from "./effectTransformPoint.js"; -import { effectBitmasks } from "./effectInfo.js"; +import Rectangle from "./Rectangle"; +import effectTransformPoint from "./effectTransformPoint"; +import { effectBitmasks } from "./effectInfo"; +import type Skin from "./Skin"; -import { Sprite, Stage } from "../Sprite.js"; +import type Renderer from "../Renderer"; +import { Sprite, Stage } from "../Sprite"; // Returns the determinant of two vectors, the vector from A to B and the vector // from A to C. If positive, it means AC is counterclockwise from AB. // If negative, AC is clockwise from AB. -const determinant = (a, b, c) => { +const determinant = ( + a: [number, number], + b: [number, number], + c: [number, number] +): number => { return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); }; @@ -18,30 +24,44 @@ const determinant = (a, b, c) => { // TODO: store renderer-specific data on the sprite and have *it* set a // "transform changed" flag. class SpriteTransformDiff { - constructor(sprite) { + private _sprite: Sprite | Stage; + private _unset: boolean; + + private _lastX: Sprite["x"] | undefined; + private _lastY: Sprite["y"] | undefined; + private _lastRotation: Sprite["direction"] | undefined; + private _lastRotationStyle: Sprite["rotationStyle"] | undefined; + private _lastSize: Sprite["size"] | undefined; + private _lastCostume!: Sprite["costume"]; + private _lastCostumeLoaded!: boolean; + + public constructor(sprite: Sprite | Stage) { this._sprite = sprite; this._unset = true; this.update(); } - update() { - this._lastX = this._sprite.x; - this._lastY = this._sprite.y; - this._lastRotation = this._sprite.direction; - this._lastRotationStyle = this._sprite.rotationStyle; - this._lastSize = this._sprite.size; + public update(): void { + if (this._sprite instanceof Sprite) { + this._lastX = this._sprite.x; + this._lastY = this._sprite.y; + this._lastRotation = this._sprite.direction; + this._lastRotationStyle = this._sprite.rotationStyle; + this._lastSize = this._sprite.size; + } this._lastCostume = this._sprite.costume; this._lastCostumeLoaded = this._sprite.costume.img.complete; this._unset = false; } - get changed() { + public get changed(): boolean { return ( - this._lastX !== this._sprite.x || - this._lastY !== this._sprite.y || - this._lastRotation !== this._sprite.direction || - this._lastRotationStyle !== this._sprite.rotationStyle || - this._lastSize !== this._sprite.size || + (this._sprite instanceof Sprite && + (this._lastX !== this._sprite.x || + this._lastY !== this._sprite.y || + this._lastRotation !== this._sprite.direction || + this._lastRotationStyle !== this._sprite.rotationStyle || + this._lastSize !== this._sprite.size)) || this._lastCostume !== this._sprite.costume || this._lastCostumeLoaded !== this._sprite.costume.img.complete || this._unset @@ -51,7 +71,24 @@ class SpriteTransformDiff { // Renderer-specific data for an instance (the original or a clone) of a Sprite export default class Drawable { - constructor(renderer, sprite) { + private _renderer: Renderer; + // TODO: make this private + public _sprite: Sprite | Stage; + private _matrix: MatrixType; + private _matrixDiff: SpriteTransformDiff; + + private _convexHullImageData: ImageData | null; + private _convexHullMosaic: number; + private _convexHullPixelate: number; + private _convexHullWhirl: number; + private _convexHullFisheye: number; + private _convexHullPoints: [number, number][] | null; + + private _aabb: Rectangle; + private _tightBoundingBox: Rectangle; + private _convexHullMatrixDiff: SpriteTransformDiff; + + public constructor(renderer: Renderer, sprite: Sprite | Stage) { this._renderer = renderer; this._sprite = sprite; @@ -79,30 +116,38 @@ export default class Drawable { this._convexHullMatrixDiff = new SpriteTransformDiff(sprite); } - getCurrentSkin() { + public getCurrentSkin(): Skin { return this._renderer._getSkin(this._sprite.costume); } // Get the rough axis-aligned bounding box for this sprite. Not as tight as // getTightBoundingBox, especially when rotated. - getAABB() { + public getAABB(): Rectangle { return Rectangle.fromMatrix(this.getMatrix(), this._aabb); } // Get the Scratch-space tight bounding box for this sprite. - getTightBoundingBox() { + public getTightBoundingBox(): Rectangle { if (!this._convexHullMatrixDiff.changed) return this._tightBoundingBox; const matrix = this.getMatrix(); const convexHullPoints = this._calculateConvexHull(); // Maybe the costume isn't loaded yet. Return a 0x0 bounding box around the // center of the sprite. - if (convexHullPoints === null) { + if (convexHullPoints === null || this._convexHullImageData === null) { + if (this._sprite instanceof Stage) { + return Rectangle.fromBounds( + this._sprite.width / -2, + this._sprite.width / 2, + this._sprite.height / -2, + this._sprite.height / 2 + ); + } return Rectangle.fromBounds( this._sprite.x, - this._sprite.y, this._sprite.x, this._sprite.y, + this._sprite.y, this._tightBoundingBox ); } @@ -111,7 +156,7 @@ export default class Drawable { let right = -Infinity; let top = -Infinity; let bottom = Infinity; - const transformedPoint = [0, 0]; + const transformedPoint: [number, number] = [0, 0]; // Each convex hull point is the center of a pixel. However, said pixels // each have area. We must take into account the size of the pixels when @@ -146,7 +191,7 @@ export default class Drawable { return this._tightBoundingBox; } - _calculateConvexHull() { + private _calculateConvexHull(): [number, number][] | null { const sprite = this._sprite; const skin = this.getCurrentSkin(); const imageData = skin.getImageData( @@ -175,14 +220,14 @@ export default class Drawable { effectBitmasks.whirl | effectBitmasks.fisheye); - const leftHull = []; - const rightHull = []; + const leftHull: [number, number][] = []; + const rightHull: [number, number][] = []; const { width, height, data } = imageData; - const pixelPos = [0, 0]; - const effectPos = [0, 0]; - let currentPoint; + const pixelPos: [number, number] = [0, 0]; + const effectPos: [number, number] = [0, 0]; + let currentPoint: [number, number] | undefined; // Not Scratch-space: y increases as we go downwards // Loop over all rows of pixels in the costume, starting at the top for (let y = 0; y < height; y++) { @@ -208,7 +253,7 @@ export default class Drawable { } // There are no opaque pixels on this row. Go to the next one. - if (x >= width) continue; + if (x >= width || !currentPoint) continue; // If appending the current point to the left hull makes a // counterclockwise turn, we want to append the current point to it. @@ -283,7 +328,7 @@ export default class Drawable { return this._convexHullPoints; } - _calculateSpriteMatrix() { + private _calculateSpriteMatrix(): void { const m = this._matrix; Matrix.identity(m); const spr = this._sprite; @@ -325,7 +370,7 @@ export default class Drawable { this._matrixDiff.update(); } - getMatrix() { + public getMatrix(): MatrixType { // If all the values we used to calculate the matrix haven't changed since // we last calculated the matrix, we can just return the matrix as-is. if (this._matrixDiff.changed) { diff --git a/src/renderer/Matrix.js b/src/renderer/Matrix.ts similarity index 77% rename from src/renderer/Matrix.js rename to src/renderer/Matrix.ts index 44fae75..309f851 100644 --- a/src/renderer/Matrix.js +++ b/src/renderer/Matrix.ts @@ -5,14 +5,14 @@ // 3x3 transform matrix operations, unrolled 4 da speedz. export default class Matrix { // Create a new 3x3 transform matrix, initialized to the identity matrix. - static create() { + public static create(): MatrixType { const matrix = new Float32Array(9); Matrix.identity(matrix); return matrix; } // Reset a matrix to the identity matrix - static identity(dst) { + public static identity(dst: MatrixType): MatrixType { dst[0] = 1; dst[1] = 0; dst[2] = 0; @@ -26,7 +26,12 @@ export default class Matrix { } // Translate a matrix by the given X and Y values - static translate(dst, src, x, y) { + public static translate( + dst: MatrixType, + src: MatrixType, + x: number, + y: number + ): MatrixType { const a00 = src[0], a01 = src[1], a02 = src[2], @@ -52,7 +57,11 @@ export default class Matrix { } // Rotate a matrix, in radians - static rotate(dst, src, rad) { + public static rotate( + dst: MatrixType, + src: MatrixType, + rad: number + ): MatrixType { const a00 = src[0], a01 = src[1], a02 = src[2], @@ -80,7 +89,12 @@ export default class Matrix { } // Scale a matrix by the given X and Y values - static scale(dst, src, x, y) { + public static scale( + dst: MatrixType, + src: MatrixType, + x: number, + y: number + ): MatrixType { dst[0] = x * src[0]; dst[1] = x * src[1]; dst[2] = x * src[2]; @@ -96,7 +110,11 @@ export default class Matrix { } // Transform a 2D point by the given matrix - static transformPoint(m, dst, src) { + public static transformPoint( + m: MatrixType, + dst: [number, number], + src: [number, number] + ): [number, number] { const x = src[0]; const y = src[1]; dst[0] = m[0] * x + m[3] * y + m[6]; @@ -104,3 +122,5 @@ export default class Matrix { return dst; } } + +export type MatrixType = Float32Array; diff --git a/src/renderer/PenSkin.js b/src/renderer/PenSkin.ts similarity index 80% rename from src/renderer/PenSkin.js rename to src/renderer/PenSkin.ts index 542c817..2a4b6df 100644 --- a/src/renderer/PenSkin.js +++ b/src/renderer/PenSkin.ts @@ -1,8 +1,18 @@ -import Skin from "./Skin.js"; -import ShaderManager from "./ShaderManager.js"; +import Skin from "./Skin"; +import ShaderManager from "./ShaderManager"; +import type Color from "../Color"; +import type { RGBANormalized } from "../Color"; +import type Renderer from "../Renderer"; +import type { FramebufferInfo } from "../Renderer"; export default class PenSkin extends Skin { - constructor(renderer, width, height) { + public _framebufferInfo: FramebufferInfo; + private _lastPenState: { + size: number; + color: RGBANormalized; + }; + + public constructor(renderer: Renderer, width: number, height: number) { super(renderer); this.width = width; this.height = height; @@ -16,23 +26,32 @@ export default class PenSkin extends Skin { this._lastPenState = { size: 0, - color: [0, 0, 0, 0] + color: [0, 0, 0, 0], }; this.clear(); } - destroy() { + public destroy(): void { const gl = this.gl; gl.deleteTexture(this._framebufferInfo.texture); gl.deleteFramebuffer(this._framebufferInfo.framebuffer); } - getTexture() { + public getTexture(): WebGLTexture { return this._framebufferInfo.texture; } - penLine(pt1, pt2, color, size) { + public getImageData(): ImageData | null { + return null; + } + + public penLine( + pt1: { x: number; y: number }, + pt2: { x: number; y: number }, + color: Color, + size: number + ): void { const renderer = this.renderer; renderer._setFramebuffer(this._framebufferInfo); @@ -102,7 +121,7 @@ export default class PenSkin extends Skin { gl.drawArrays(gl.TRIANGLES, 0, 6); } - clear() { + public clear(): void { this.renderer._setFramebuffer(this._framebufferInfo); const gl = this.gl; gl.clearColor(0, 0, 0, 0); diff --git a/src/renderer/Rectangle.js b/src/renderer/Rectangle.ts similarity index 73% rename from src/renderer/Rectangle.js rename to src/renderer/Rectangle.ts index 149bfa4..66a668e 100644 --- a/src/renderer/Rectangle.js +++ b/src/renderer/Rectangle.ts @@ -1,5 +1,12 @@ +import type { MatrixType } from "./Matrix"; + export default class Rectangle { - constructor() { + public left: number; + public right: number; + public bottom: number; + public top: number; + + public constructor() { this.left = -Infinity; this.right = Infinity; this.bottom = -Infinity; @@ -8,8 +15,13 @@ export default class Rectangle { return this; } - static fromBounds(left, right, bottom, top, result) { - if (!result) result = new Rectangle(); + public static fromBounds( + left: number, + right: number, + bottom: number, + top: number, + result = new Rectangle() + ): Rectangle { result.left = left; result.right = right; result.bottom = bottom; @@ -19,9 +31,10 @@ export default class Rectangle { } // Initialize a bounding box around a sprite given the sprite's transform matrix. - static fromMatrix(matrix, result) { - if (!result) result = new Rectangle(); - + public static fromMatrix( + matrix: MatrixType, + result = new Rectangle() + ): Rectangle { // Adapted somewhat from https://github.com/LLK/scratch-render/blob/develop/docs/Rectangle-AABB-Matrix.md const xa = matrix[0] / 2; const xb = matrix[3] / 2; @@ -42,7 +55,7 @@ export default class Rectangle { } // Initialize from another rectangle. - static copy(src, dst) { + public static copy(src: Rectangle, dst: Rectangle): Rectangle { dst.left = src.left; dst.right = src.right; dst.bottom = src.bottom; @@ -52,7 +65,7 @@ export default class Rectangle { // Push this rectangle out to integer bounds. // This takes a conservative approach and will always expand the rectangle outwards. - snapToInt() { + public snapToInt(): this { this.left = Math.floor(this.left); this.right = Math.ceil(this.right); this.bottom = Math.floor(this.bottom); @@ -62,7 +75,7 @@ export default class Rectangle { } // Check whether any part of this rectangle touches another rectangle. - intersects(rect) { + public intersects(rect: Rectangle): boolean { return ( this.left <= rect.right && rect.left <= this.right && @@ -72,14 +85,14 @@ export default class Rectangle { } // Check whether a given point is inside this rectangle. - containsPoint(x, y) { + public containsPoint(x: number, y: number): boolean { return ( x >= this.left && x <= this.right && y >= this.bottom && y <= this.top ); } // Clamp this rectangle within bounds. - clamp(left, right, bottom, top) { + public clamp(left: number, right: number, bottom: number, top: number): this { this.left = Math.min(Math.max(this.left, left), right); this.right = Math.max(Math.min(this.right, right), left); this.bottom = Math.min(Math.max(this.bottom, bottom), top); @@ -89,7 +102,11 @@ export default class Rectangle { } // Compute the union of two rectangles. - static union(rect1, rect2, result = new Rectangle()) { + public static union( + rect1: Rectangle, + rect2: Rectangle, + result = new Rectangle() + ): Rectangle { result.left = Math.min(rect1.left, rect2.left); result.right = Math.max(rect1.right, rect2.right); result.bottom = Math.min(rect1.bottom, rect2.bottom); @@ -99,7 +116,11 @@ export default class Rectangle { } // Compute the intersection of two rectangles. - static intersection(rect1, rect2, result = new Rectangle()) { + public static intersection( + rect1: Rectangle, + rect2: Rectangle, + result = new Rectangle() + ): Rectangle { result.left = Math.max(rect1.left, rect2.left); result.right = Math.min(rect1.right, rect2.right); result.bottom = Math.max(rect1.bottom, rect2.bottom); @@ -108,11 +129,11 @@ export default class Rectangle { return result; } - get width() { + public get width(): number { return this.right - this.left; } - get height() { + public get height(): number { return this.top - this.bottom; } } diff --git a/src/renderer/ShaderManager.js b/src/renderer/ShaderManager.js deleted file mode 100644 index 4118d2d..0000000 --- a/src/renderer/ShaderManager.js +++ /dev/null @@ -1,135 +0,0 @@ -import { SpriteShader, PenLineShader } from "./Shaders.js"; -import { effectNames, effectBitmasks } from "./effectInfo.js"; - -// Everything contained in a shader. It contains both the program, and the locations of the shader inputs. -class Shader { - constructor(gl, program) { - this.gl = gl; - this.program = program; - this.uniforms = {}; - this.attribs = {}; - - // In order to pass a value into a shader as an attribute or uniform, you need to know its location. - // This maps the names of attributes and uniforms to their locations, accessible via the `uniforms` and `attribs` - // properties. - const numActiveUniforms = gl.getProgramParameter( - program, - gl.ACTIVE_UNIFORMS - ); - for (let i = 0; i < numActiveUniforms; i++) { - const { name } = gl.getActiveUniform(program, i); - this.uniforms[name] = gl.getUniformLocation(program, name); - } - - const numActiveAttributes = gl.getProgramParameter( - program, - gl.ACTIVE_ATTRIBUTES - ); - for (let i = 0; i < numActiveAttributes; i++) { - const { name } = gl.getActiveAttrib(program, i); - this.attribs[name] = gl.getAttribLocation(program, name); - } - } -} - -class ShaderManager { - constructor(renderer) { - this.renderer = renderer; - this.gl = renderer.gl; - - // We compile shaders on-demand. Create one shader cache per draw mode. - this._shaderCache = {}; - for (const drawMode of Object.keys(ShaderManager.DrawModes)) { - this._shaderCache[drawMode] = new Map(); - } - } - - // Creates and compiles a vertex or fragment shader from the given source code. - _createShader(source, type) { - const gl = this.gl; - const shader = gl.createShader(type); - gl.shaderSource(shader, source); - gl.compileShader(shader); - - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - const info = gl.getShaderInfoLog(shader); - throw "Could not compile WebGL program. \n" + info; - } - - return shader; - } - - getShader(drawMode, effectBitmask = 0) { - const gl = this.gl; - // Each combination of enabled effects is compiled to a different shader, with only the needed effect code. - // Check if we've already compiled the shader with this set of enabled effects. - const shaderMap = this._shaderCache[drawMode]; - if (shaderMap.has(effectBitmask)) { - return shaderMap.get(effectBitmask); - } else { - let shaderCode; - switch (drawMode) { - case ShaderManager.DrawModes.PEN_LINE: { - shaderCode = PenLineShader; - break; - } - default: { - shaderCode = SpriteShader; - break; - } - } - - // Use #define statements for conditional compilation in shader code. - let define = `#define DRAW_MODE_${drawMode}\n`; - - // Add #defines for each enabled effect. - for (let i = 0; i < effectNames.length; i++) { - const effectName = effectNames[i]; - if ((effectBitmask & effectBitmasks[effectName]) !== 0) { - define += `#define EFFECT_${effectName}\n`; - } - } - - const vertShader = this._createShader( - define + shaderCode.vertex, - gl.VERTEX_SHADER - ); - const fragShader = this._createShader( - define + shaderCode.fragment, - gl.FRAGMENT_SHADER - ); - - // Combine the vertex and fragment shaders into a single GL program. - const program = gl.createProgram(); - gl.attachShader(program, vertShader); - gl.attachShader(program, fragShader); - gl.linkProgram(program); - - if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { - const info = gl.getProgramInfoLog(program); - throw new Error("Could not compile WebGL program. \n" + info); - } - - const shader = new Shader(gl, program); - shaderMap.set(effectBitmask, shader); - return shader; - } - } -} - -ShaderManager.DrawModes = { - // Used for drawing sprites normally - DEFAULT: "DEFAULT", - // Used for "touching" tests. Discards transparent pixels. - SILHOUETTE: "SILHOUETTE", - // Used for "color is touching color" tests. Only renders sprite colors which are close to the color passed in, and - // discards all pixels of a different color. - COLOR_MASK: "COLOR_MASK", - // Used for picking the topmost sprite and identifying which one it is. - // Assigns a color to each sprite. - SPRITE_ID: "SPRITE_ID", - // Used for drawing pen lines. - PEN_LINE: "PEN_LINE" -}; - -export default ShaderManager; diff --git a/src/renderer/ShaderManager.ts b/src/renderer/ShaderManager.ts new file mode 100644 index 0000000..8ed6849 --- /dev/null +++ b/src/renderer/ShaderManager.ts @@ -0,0 +1,157 @@ +import { SpriteShader, PenLineShader } from "./Shaders"; +import { effectNames, effectBitmasks } from "./effectInfo"; +import type Renderer from "../Renderer"; + +// Everything contained in a shader. It contains both the program, and the locations of the shader inputs. +class Shader { + private gl: WebGLRenderingContext; + public program: WebGLProgram; + // TODO: strongly type these + public uniforms: Record; + public attribs: Record; + + public constructor(gl: WebGLRenderingContext, program: WebGLProgram) { + this.gl = gl; + this.program = program; + this.uniforms = {}; + this.attribs = {}; + + // In order to pass a value into a shader as an attribute or uniform, you need to know its location. + // This maps the names of attributes and uniforms to their locations, accessible via the `uniforms` and `attribs` + // properties. + const numActiveUniforms = gl.getProgramParameter( + program, + gl.ACTIVE_UNIFORMS + ) as number; + for (let i = 0; i < numActiveUniforms; i++) { + const { name } = gl.getActiveUniform(program, i)!; + this.uniforms[name] = gl.getUniformLocation(program, name)!; + } + + const numActiveAttributes = gl.getProgramParameter( + program, + gl.ACTIVE_ATTRIBUTES + ) as number; + for (let i = 0; i < numActiveAttributes; i++) { + const { name } = gl.getActiveAttrib(program, i)!; + this.attribs[name] = gl.getAttribLocation(program, name)!; + } + } +} + +type DrawMode = keyof typeof ShaderManager["DrawModes"]; + +class ShaderManager { + private renderer: Renderer; + private gl: WebGLRenderingContext; + + private _shaderCache: Record>; + + public constructor(renderer: Renderer) { + this.renderer = renderer; + this.gl = renderer.gl; + + // We compile shaders on-demand. Create one shader cache per draw mode. + this._shaderCache = {} as Record>; + for (const drawMode of Object.keys(ShaderManager.DrawModes)) { + this._shaderCache[drawMode as DrawMode] = new Map(); + } + } + + // Creates and compiles a vertex or fragment shader from the given source code. + private _createShader( + source: string, + type: + | WebGLRenderingContext["FRAGMENT_SHADER"] + | WebGLRenderingContext["VERTEX_SHADER"] + ): WebGLShader { + const gl = this.gl; + const shader = gl.createShader(type); + if (!shader) throw new Error("Could not create shader."); + gl.shaderSource(shader, source); + gl.compileShader(shader); + + if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { + const info = gl.getShaderInfoLog(shader) ?? ""; + throw new Error("Could not compile WebGL program. \n" + info); + } + + return shader; + } + + public getShader(drawMode: DrawMode, effectBitmask = 0): Shader { + const gl = this.gl; + // Each combination of enabled effects is compiled to a different shader, with only the needed effect code. + // Check if we've already compiled the shader with this set of enabled effects. + const shaderMap = this._shaderCache[drawMode]; + const existingShader = shaderMap.get(effectBitmask); + if (existingShader) return existingShader; + + let shaderCode; + switch (drawMode) { + case ShaderManager.DrawModes.PEN_LINE: { + shaderCode = PenLineShader; + break; + } + default: { + shaderCode = SpriteShader; + break; + } + } + + // Use #define statements for conditional compilation in shader code. + let define = `#define DRAW_MODE_${drawMode}\n`; + + // Add #defines for each enabled effect. + for (let i = 0; i < effectNames.length; i++) { + const effectName = effectNames[i]; + if ((effectBitmask & effectBitmasks[effectName]) !== 0) { + define += `#define EFFECT_${effectName}\n`; + } + } + + const vertShader = this._createShader( + define + shaderCode.vertex, + gl.VERTEX_SHADER + ); + const fragShader = this._createShader( + define + shaderCode.fragment, + gl.FRAGMENT_SHADER + ); + + // Combine the vertex and fragment shaders into a single GL program. + const program = gl.createProgram(); + if (!program) throw new Error("Could not create program"); + gl.attachShader(program, vertShader); + gl.attachShader(program, fragShader); + gl.linkProgram(program); + + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + const info = gl.getProgramInfoLog(program) ?? ""; + throw new Error("Could not compile WebGL program. \n" + info); + } + + const shader = new Shader(gl, program); + shaderMap.set(effectBitmask, shader); + return shader; + } + + public static DrawModes = { + // Used for drawing sprites normally + DEFAULT: "DEFAULT", + // Used for "touching" tests. Discards transparent pixels. + SILHOUETTE: "SILHOUETTE", + // Used for "color is touching color" tests. Only renders sprite colors which are close to the color passed in, and + // discards all pixels of a different color. + COLOR_MASK: "COLOR_MASK", + // Used for picking the topmost sprite and identifying which one it is. + // Assigns a color to each sprite. + SPRITE_ID: "SPRITE_ID", + // Used for drawing pen lines. + PEN_LINE: "PEN_LINE", + } as const; +} + +export default ShaderManager; +export { Shader }; +export type { DrawMode }; diff --git a/src/renderer/Shaders.js b/src/renderer/Shaders.ts similarity index 96% rename from src/renderer/Shaders.js rename to src/renderer/Shaders.ts index b443ec6..2dceae7 100644 --- a/src/renderer/Shaders.js +++ b/src/renderer/Shaders.ts @@ -1,6 +1,4 @@ -const SpriteShader = {}; - -SpriteShader.vertex = ` +const spriteShaderVertex = ` precision mediump float; attribute vec2 a_position; @@ -15,7 +13,7 @@ void main() { } `; -SpriteShader.fragment = ` +const spriteShaderFragment = ` precision mediump float; const float epsilon = 1e-3; @@ -184,10 +182,12 @@ void main() { gl_FragColor = color; } `; +const SpriteShader = { + vertex: spriteShaderVertex, + fragment: spriteShaderFragment, +}; -const PenLineShader = {}; - -PenLineShader.vertex = ` +const penLineShaderVertex = ` precision mediump float; attribute vec2 a_position; @@ -249,7 +249,7 @@ void main() { } `; -PenLineShader.fragment = ` +const penLineShaderFragment = ` precision mediump float; uniform sampler2D u_texture; @@ -280,4 +280,9 @@ void main() { } `; +const PenLineShader = { + vertex: penLineShaderVertex, + fragment: penLineShaderFragment, +}; + export { SpriteShader, PenLineShader }; diff --git a/src/renderer/Skin.js b/src/renderer/Skin.ts similarity index 55% rename from src/renderer/Skin.js rename to src/renderer/Skin.ts index dc709f6..0f29528 100644 --- a/src/renderer/Skin.js +++ b/src/renderer/Skin.ts @@ -1,25 +1,40 @@ -export default class Skin { - constructor(renderer) { +import type Renderer from "../Renderer"; + +export default abstract class Skin { + protected renderer: Renderer; + protected gl: WebGLRenderingContext; + public width: number; + public height: number; + + public constructor(renderer: Renderer) { this.renderer = renderer; this.gl = renderer.gl; + this.width = 0; + this.height = 0; } - // Get the skin's texture for a given (screen-space) scale. - /* eslint-disable-next-line no-unused-vars */ - getTexture(scale) { - return null; - } + /** + * Get the skin's texture at a given screen-space scale. + * @param scale The screen-space scale factor for the texture, as a ratio of screen pixels to texture pixels. + */ + public abstract getTexture(scale: number): WebGLTexture | null; - // Get the skin image's ImageData at a given (screen-space) scale. - // eslint-disable-next-line no-unused-vars - getImageData(scale) { - throw new Error("getImageData not implemented for this skin type"); - } + /** + * Gets the raster ImageData for a skin's texture at a given screen-space scale. + * @param scale The screen-space scale factor for the texture, as a ratio of screen pixels to texture pixels. + */ + public abstract getImageData(scale: number): ImageData | null; // Helper function to create a texture from an image and handle all the boilerplate. - _makeTexture(image, filtering) { + protected _makeTexture( + image: HTMLImageElement | HTMLCanvasElement | null, + filtering: + | WebGLRenderingContext["NEAREST"] + | WebGLRenderingContext["LINEAR"] + ): WebGLTexture { const gl = this.gl; const glTexture = gl.createTexture(); + if (!glTexture) throw new Error("Could not create texture"); gl.bindTexture(gl.TEXTURE_2D, glTexture); // These need to be set because most sprite textures don't have power-of-two dimensions. // Non-power-of-two textures only work with gl.CLAMP_TO_EDGE wrapping behavior, @@ -42,7 +57,7 @@ export default class Skin { } // Helper function to set this skin's size based on an image that may or may not be loaded. - _setSizeFromImage(image) { + protected _setSizeFromImage(image: HTMLImageElement): void { if (image.complete) { this.width = image.naturalWidth; this.height = image.naturalHeight; @@ -55,5 +70,5 @@ export default class Skin { } // Clean up any textures or other objets created by this skin. - destroy() {} + public abstract destroy(): void; } diff --git a/src/renderer/SpeechBubbleSkin.js b/src/renderer/SpeechBubbleSkin.ts similarity index 71% rename from src/renderer/SpeechBubbleSkin.js rename to src/renderer/SpeechBubbleSkin.ts index c5878cf..bb2f9ae 100644 --- a/src/renderer/SpeechBubbleSkin.js +++ b/src/renderer/SpeechBubbleSkin.ts @@ -1,19 +1,34 @@ -import Skin from "./Skin.js"; +import Skin from "./Skin"; +import type Renderer from "../Renderer"; +import type { SpeechBubble, SpeechBubbleStyle } from "../Sprite"; const bubbleStyle = { maxLineWidth: 170, minWidth: 50, strokeWidth: 4, padding: 12, - tailHeight: 12 -}; + tailHeight: 12, +} as const; // TODO: multiline speech bubbles export default class SpeechBubbleSkin extends Skin { - constructor(renderer, bubble) { + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + private _texture: WebGLTexture; + private _bubble: SpeechBubble; + private _flipped: boolean; + private _rendered: boolean; + private _renderedScale: number; + public offsetX: number; + public offsetY: number; + + public constructor(renderer: Renderer, bubble: SpeechBubble) { super(renderer); this._canvas = document.createElement("canvas"); + const ctx = this._canvas.getContext("2d"); + if (ctx === null) throw new Error("Could not get canvas context"); + this._ctx = ctx; this._texture = this._makeTexture(null, this.gl.LINEAR); this._bubble = bubble; this._flipped = false; @@ -24,27 +39,36 @@ export default class SpeechBubbleSkin extends Skin { this.height = 0; this.offsetX = -bubbleStyle.strokeWidth / 2; this.offsetY = this.offsetX + bubbleStyle.tailHeight; - - this._renderBubble(this._bubble); } // To ensure proper text measurement and drawing, it's necessary to restyle the canvas after resizing it. - _restyleCanvas() { - const ctx = this._canvas.getContext("2d"); + private _restyleCanvas(): void { + const ctx = this._ctx; ctx.font = "16px sans-serif"; ctx.textBaseline = "hanging"; } - set flipped(flipped) { + public get flipped(): boolean { + return this._flipped; + } + + public set flipped(flipped) { this._flipped = flipped; this._rendered = false; } - _renderBubble(bubble, scale) { + private _renderBubble(bubble: SpeechBubble, scale: number): void { const canvas = this._canvas; - const ctx = canvas.getContext("2d"); - - const renderBubbleBackground = (x, y, w, h, r, style) => { + const ctx = this._ctx; + + const renderBubbleBackground = ( + x: number, + y: number, + w: number, + h: number, + r: number, + style: SpeechBubbleStyle + ): void => { if (r > w / 2) r = w / 2; if (r > h / 2) r = h / 2; if (r < 0) return; @@ -57,7 +81,7 @@ export default class SpeechBubbleSkin extends Skin { ctx.lineTo(Math.min(x + 3 * r, x + w - r), y + h); ctx.lineTo(x + r / 2, y + h + r); ctx.lineTo(x + r, y + h); - } else if (style === "think") { + } else { ctx.ellipse(x + r * 2.25, y + h, (r * 3) / 4, r / 2, 0, 0, Math.PI); } ctx.arcTo(x, y + h, x, y, r); @@ -124,7 +148,7 @@ export default class SpeechBubbleSkin extends Skin { this._renderedScale = scale; } - getTexture(scale) { + public getTexture(scale: number): WebGLTexture { if (!this._rendered || this._renderedScale !== scale) { this._renderBubble(this._bubble, scale); const gl = this.gl; @@ -142,7 +166,17 @@ export default class SpeechBubbleSkin extends Skin { return this._texture; } - destroy() { + public getImageData(scale: number): ImageData | null { + this.getTexture(scale); + return this._ctx.getImageData( + 0, + 0, + this._canvas.width, + this._canvas.height + ); + } + + public destroy(): void { this.gl.deleteTexture(this._texture); } } diff --git a/src/renderer/VectorSkin.js b/src/renderer/VectorSkin.ts similarity index 69% rename from src/renderer/VectorSkin.js rename to src/renderer/VectorSkin.ts index 21210ef..065b403 100644 --- a/src/renderer/VectorSkin.js +++ b/src/renderer/VectorSkin.ts @@ -1,51 +1,62 @@ -import Skin from "./Skin.js"; +import Skin from "./Skin"; +import type Renderer from "../Renderer"; // This means that the smallest mipmap will be 1/(2**4)th the size of the sprite's "100%" size. const MIPMAP_OFFSET = 4; export default class VectorSkin extends Skin { - constructor(renderer, image) { + private _image: HTMLImageElement; + private _canvas: HTMLCanvasElement; + private _ctx: CanvasRenderingContext2D; + private _imageDataMipLevel: number; + private _imageData: ImageData | null; + private _maxTextureSize: number; + private _mipmaps: Map; + + public constructor(renderer: Renderer, image: HTMLImageElement) { super(renderer); this._image = image; this._canvas = document.createElement("canvas"); + const ctx = this._canvas.getContext("2d"); + if (!ctx) throw new Error("Could not get canvas context"); + this._ctx = ctx; this._imageDataMipLevel = 0; this._imageData = null; this._maxTextureSize = renderer.gl.getParameter( renderer.gl.MAX_TEXTURE_SIZE - ); + ) as number; this._setSizeFromImage(image); this._mipmaps = new Map(); } - static mipLevelForScale(scale) { + private static mipLevelForScale(scale: number): number { return Math.max(Math.ceil(Math.log2(scale)) + MIPMAP_OFFSET, 0); } - getImageData(scale) { + public getImageData(scale: number): ImageData | null { if (!this._image.complete) return null; // Round off the scale of the image data drawn to a given power-of-two mip level. const mipLevel = VectorSkin.mipLevelForScale(scale); if (!this._imageData || this._imageDataMipLevel !== mipLevel) { - const canvas = this._drawSvgToCanvas(mipLevel); - if (canvas === null) return null; + const ctx = this._drawSvgToCanvas(mipLevel); + if (ctx === null) return null; + const { canvas } = ctx; // Cache image data so we can reuse it - this._imageData = canvas - .getContext("2d") - .getImageData(0, 0, canvas.width, canvas.height); + this._imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); this._imageDataMipLevel = mipLevel; } return this._imageData; } - _drawSvgToCanvas(mipLevel) { + private _drawSvgToCanvas(mipLevel: number): CanvasRenderingContext2D | null { const scale = 2 ** (mipLevel - MIPMAP_OFFSET); const image = this._image; @@ -61,30 +72,30 @@ export default class VectorSkin extends Skin { } // Instead of uploading the image to WebGL as a texture, render the image to a canvas and upload the canvas. - const canvas = this._canvas; - const ctx = canvas.getContext("2d"); + const ctx = this._ctx; + const { canvas } = ctx; canvas.width = width; canvas.height = height; ctx.drawImage(image, 0, 0, width, height); - return this._canvas; + return ctx; } // TODO: handle proper subpixel positioning when SVG viewbox has non-integer coordinates // This will require rethinking costume + project loading probably - _createMipmap(mipLevel) { + private _createMipmap(mipLevel: number): void { // Instead of uploading the image to WebGL as a texture, render the image to a canvas and upload the canvas. - const canvas = this._drawSvgToCanvas(mipLevel); + const ctx = this._drawSvgToCanvas(mipLevel); this._mipmaps.set( mipLevel, // Use linear (i.e. smooth) texture filtering for vectors // If the image is 0x0, we return null. Check for that. - canvas === null ? null : this._makeTexture(canvas, this.gl.LINEAR) + ctx === null ? null : this._makeTexture(ctx.canvas, this.gl.LINEAR) ); } - getTexture(scale) { + public getTexture(scale: number): WebGLTexture | null { if (!this._image.complete) return null; // Because WebGL doesn't support vector graphics, substitute a bunch of bitmaps. @@ -97,10 +108,10 @@ export default class VectorSkin extends Skin { const mipLevel = VectorSkin.mipLevelForScale(scale); if (!this._mipmaps.has(mipLevel)) this._createMipmap(mipLevel); - return this._mipmaps.get(mipLevel); + return this._mipmaps.get(mipLevel) ?? null; } - destroy() { + public destroy(): void { for (const mip of this._mipmaps.values()) { this.gl.deleteTexture(mip); } diff --git a/src/renderer/effectInfo.js b/src/renderer/effectInfo.ts similarity index 61% rename from src/renderer/effectInfo.js rename to src/renderer/effectInfo.ts index fe14cb8..e8346b2 100644 --- a/src/renderer/effectInfo.js +++ b/src/renderer/effectInfo.ts @@ -6,12 +6,17 @@ const effectNames = [ "pixelate", "mosaic", "brightness", - "ghost" -]; + "ghost", +] as const; -const effectBitmasks = {}; -for (let i = 0; i < effectNames.length; i++) { - effectBitmasks[effectNames[i]] = 1 << i; -} +const effectBitmasks = { + color: 1, + fisheye: 2, + whirl: 4, + pixelate: 8, + mosaic: 16, + brightness: 32, + ghost: 64, +} as const; export { effectNames, effectBitmasks }; diff --git a/src/renderer/effectTransformPoint.js b/src/renderer/effectTransformPoint.ts similarity index 93% rename from src/renderer/effectTransformPoint.js rename to src/renderer/effectTransformPoint.ts index bb1590d..c3f058f 100644 --- a/src/renderer/effectTransformPoint.js +++ b/src/renderer/effectTransformPoint.ts @@ -1,10 +1,15 @@ -import { effectBitmasks } from "./effectInfo.js"; +import { effectBitmasks } from "./effectInfo"; +import type Drawable from "./Drawable"; const CENTER = 0.5; const EPSILON = 1e-3; // Transform a texture-space point using the effects defined on the given drawable. -const effectTransformPoint = (drawable, src, dst) => { +const effectTransformPoint = ( + drawable: Drawable, + src: [number, number], + dst: [number, number] +): [number, number] => { const { effects } = drawable._sprite; const effectBitmask = effects._bitmask; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..d530d53 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2020", + + "esModuleInterop": true, + "moduleResolution": "node", + "sourceMap": true, + "strict": true, + "allowJs": true, + + "outDir": "dist", + "declaration": true, + "declarationDir": "dist" + }, + "include": [ + "src/**/*" + ], + "exclude": ["dist"] +}